CF961G Partitions

题意

link
求将 n 个物品划分成 k 个集合的所有方案的权值和,权值和为每个集合的大小乘集合元素的和的和。

推式子就完了

一眼得:

x=1nwxi=1ni(n1i1){nik1}

和是线性的,直接拆开算每个元素贡献,枚举它所在集合的大小 i,给它选 i1 个数,剩下 ni 个数自己分成 k1 个非空集合。

考虑拆开第二类斯特林数,有个容斥的式子:

{nm}=1m!i=0m(1)i(mi)n(mi)

=i=0m(1)i(mi)ni!(mi)!

实际上只要计算出后面的式子:

i=1ni(n1i1)j=0k1(1)j(k1j)nij!(k1j)!

后面的拆开的式子又不能合回去,那就把它移到前面:

j=0k1(1)jj!(k1j)!i=1ni(n1i1)(k1j)ni

发现 nii1 等于 n1, 但是多了 i,要不然就能用二项式定理了,那就拆开,令 i=1+(i1)

j=0k1(1)jj!(k1j)!(i=1n(n1i1)(k1j)ni+i=1n(i1)(n1i1)(k1j)ni)

那么:

i=1n(n1i1)(k1j)ni=(k1j+1)n1

注意到 (i1)(n1i1), 有这样一个组合恒等式:

m(nm)=(n1)!n(m1)!(nm)!=n(n1m1)

利用这个式子,可以把一个变量替换成不变量,是推组合式子的常用操作。

就有:

i=1n(i1)(n1i1)(k1j)ni)=i=1n(n1)(n2i2)(k1j)ni

再提出 n1:

(n1)i=1n2(n2i2)(k1j)ni

利用二项式定理,就有:

(n1)(k1j+1)n2

因此,上面的式子为:

j=0k1(1)jj!(k1j)![(kj)n1+(n1)(kj)n2]

在时间复杂度 O(nlogn) 就能算出。

不然就得用 MTT 计算第二类斯特林数的生成函数,时间复杂度是大常数 O(nlog2n), 码量爆炸。

代码

#include<bits/stdc++.h>
using namespace std;

using ll = long long;
const int MAXN = 200010;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-9; 

template <typename T>
void Read(T &x) {
	x = 0; T f = 1; char a = getchar();
	for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
	for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
	x *= f;
}

inline int add(const int &a, const int &b, const int p = mod) {
	static int c;
	c = a + b;
	if (c >= p) c -= p;
	return c; 
} 
inline int dec(const int &a, const int &b, const int p = mod) {
	static int c;
	c = a - b;
	if (c < 0) c += p;
	return c; 
} 
inline int mul(const int &a, const int &b, const int p = mod) {
	return 1ll * a * b % p; 
}
inline int qpow(int a, int b, const int p = mod) {
	int sum(1);
	while(b) {
		if (b & 1) sum = mul(sum, a, p);
		a = mul(a, a, p);
		b >>= 1; 
	}
	return sum; 
}

int n, k; 
 
int f[MAXN], ifa[MAXN]; 
int main() {
	Read(n); Read(k);
	if (n == 1) {
		int w;
		Read(w);
		return cout << w, 0 ; 
	}
	f[0] = 1; 
	for (int i = 1; i <= k - 1; i ++)	
		f[i] = mul(f[i - 1], i);
	ifa[k - 1] = qpow(f[k - 1], mod - 2);
	for (int i = k - 2; i >= 0; i --)
		ifa[i] = mul(ifa[i + 1], i + 1);
	int sum = 0;
	for (int i = 0, p = 1; i <= k - 1; i ++, p = mod - p) 
		sum = add(sum, mul(mul(mul(p, ifa[i]), ifa[k - 1 - i]), add(qpow(k - i, n - 1), mul(n - 1, qpow(k - i, n - 2))))); 
	int ans = 0; 
	for (int i = 1; i <= n; i ++) {
		int w; 
		Read(w);
		ans = add(ans, mul(w, sum));  
	}
	cout << ans; 
	
	return 0;	
} 
posted @   qjbqjb  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示