luogu CF961G Partitions

背景:

不是每天都有背景的。

题目传送门:

https://www.luogu.org/problem/CF961G

题意:

n n n个数,每一个数有一个权值 w i w_i wi,现在将这些数分成 k k k个集合,对于一种分法的贡献是: ∑ i = 1 k ∣ S i ∣ ∑ j ∈ S i w j \sum_{i=1}^{k}|S_i|\sum_{j∈S_i}w_j i=1kSijSiwj,求最后的贡献和。
具体可见样例解释。

思路 1 1 1

a n s = ∑ i = 1 n w i ∑ j = 1 n j C n − 1 j − 1 S n − j , k − 1 ans=\sum_{i=1}^{n}w_i\sum_{j=1}^{n}jC_{n-1}^{j-1}S_{n-j,k-1} ans=i=1nwij=1njCn1j1Snj,k1

理解起来也不难,就是枚举当前第 i i i个数所在的集合大小 j j j
那么可以在剩下的 n − 1 n-1 n1个数中选 j − 1 j-1 j1个数放在这个集合中,方案数为 C n − 1 j − 1 C_{n-1}^{j-1} Cn1j1
而剩下的 n − j n-j nj个数就必须放在剩下的 k − 1 k-1 k1个集合中,方案数为 S n − j , k − 1 S_{n-j,k-1} Snj,k1
最后乘上当前集合的大小即可。

可是太菜了不会化简式子。可前往大佬们的博客查找。

思路 2 2 2

容易发现答案的形式形如 p ∑ i = 1 n w i p\sum_{i=1}^{n}w_i pi=1nwi,其中 p p p为一个系数,就是我们要算的。
考虑其它的数 i i i对当前的数 x x x的贡献。
x = i x=i x=i,则可以任意选择,因此方案数为 S n , k S_{n,k} Sn,k
x ≠ i x≠i x̸=i,当且仅当 x x x i i i在同一个人集合时才会有贡献,因此在剩下的 n − 1 n-1 n1个数中选择一个数 i i i,方案数为 n − 1 n-1 n1;剩下的 n − 1 n-1 n1个数(除了 x x x以外的数)本身就可以组成 k k k(不可能组成 k − 1 k-1 k1个集合,因为此时 x ≠ i x≠i x̸=i,自己对自己的贡献已经被计算过了)个集合,方案数为 S n − 1 , k S_{n-1,k} Sn1,k
因此有:
a n s = ( S n , k + ( n − 1 ) S n − 1 , k ) ∑ i = 1 n w i ans=\big(S_{n,k}+(n-1)S_{n-1,k}\big)\sum_{i=1}^{n}w_i ans=(Sn,k+(n1)Sn1,k)i=1nwi

考虑 S n , m = ∑ i = 0 m ( − 1 ) i i ! ( m − i ) n ( m − i ) ! S_{n,m}=\sum_{i=0}^{m}\frac{(-1)^i}{i!}\frac{(m-i)^{n}}{(m-i)!} Sn,m=i=0mi!(1)i(mi)!(mi)n Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)求解即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define mod 1000000007
using namespace std;
	int n,k;
	int fac[200010],inv[200010],Inv[200010];
	int sum=0;
void init()
{
	fac[0]=fac[1]=1;
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)
	{
		fac[i]=(LL)fac[i-1]*i%mod;
		inv[i]=((LL)mod-mod/i)*inv[mod%i]%mod;
	}
	Inv[0]=Inv[1]=1;
	for(int i=2;i<=n;i++)
		Inv[i]=(LL)Inv[i-1]*inv[i]%mod;
}
int ksm(int x,int k)
{
	int tot=1;
	for(;k;k>>=1)
	{
		if(k&1) tot=(LL)tot*x%mod;
		x=(LL)x*x%mod;	
	}
	return tot;
}
int calc_S(int n,int m)
{
	int sum=0;
	for(int i=0;i<=m;i++)
		sum=((LL)sum+((i&1)?-1ll:1ll)*Inv[i]*ksm(m-i,n)%mod*Inv[m-i]%mod+mod)%mod;
	return sum;
}
int main()
{
	int x;
	scanf("%d %d",&n,&k);
	init();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		sum=((LL)sum+x)%mod;
	}
	printf("%d",((LL)calc_S(n,k)+(LL)(n-1)*calc_S(n-1,k)%mod)%mod*sum%mod);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值