8.27 神异之旅

题意

对于所有大小为\(k\)正整数可重集合,要求\(\sum_{i=1}^ka_i =n\)

定义满足上述要求的正整数可重集合的权值为

\[\sum_{i=1}^k a_i^m \]

求所有满足条件的集合的权值之和,模\(10^9+7\)

其实luogu上有原题,\(P4977\),那道题数据范围更大,还需要滚动数组优化


解法

由于权值是相加进行计算的,我们可以把每一部分的权值分开计算

比如说对于一个数\(a_i\)

它在所有的集合中出现的次数之和为\(cnt_i\)

那么这个数\(a_i\)做出的贡献就是\(a_i^m \times cnt_i\)

现在的问题就是如何求出这个\(cnt_i\)

再次转化一下这个问题,我们可以\(O(N)\)枚举这个数在集合中出现的次数\(x\),乘上出现次数为\(x\)的集合个数,累加起来即为最终的答案

现在的问题转化为了求出现次数为\(x\)的集合个数

\(f[n][k]\)为权值和为\(n\),共有\(k\)个元素的集合个数,那么出现次数至少为\(x\)的集合个数即为\(f[n-x\times a_i][k-x]\)

但是上面这个是出现至少为\(x\)次的,并不是恰好为\(x\)次的

这个问题怎么解决呢

可以想到用容斥,但是有一个更简单的方法

我们最后要求的是\(cnt_i\),原来我们的想法是出现次数乘上出现次数是该数的集合个数:

现在我们只需要累加出现次数是该数的集合个数了

这样相当于每次加一层,加完就统计出所有答案了

问题再一次转化(保证是最后一次了),我们现在的任务是求出\(f[n][k]\)

考虑进行\(DP\),具体问题可参考\(P1025\)数的划分

这个\(DP\)的思路很巧妙

我们把一个状态分成两类:一类其集合中有\(1\)这个元素,一类中没有,两类分别转移相加

具体来说就是

\(f[n][k]=f[n][k]+f[n-1][k-1]\)

这一类是集合内有\(1\)的个数

\(f[n][k]=f[n][k]+f[n-k][k]\)

这一类是集合内无\(1\)的个数,可以看成是\(f[n-k][k]\)中所有的元素都加一个\(1\),这样就可以保证集合内无\(1\)


代码

#include <cstdio>
#include <cctype>

using namespace std;

const int mod = 1e9 + 7;

int n, k, m;

int f[5010][5010];

inline int qpow(int x, int y) {
	int res = 1;
	while (y) {
		if (y & 1)	res = 1LL * res * x % mod;
		x = 1LL * x * x % mod, y >>= 1;	
	}
	return res;
}

int main() {
	
	freopen("set.in", "r", stdin);
	freopen("set.out", "w", stdout);
	
	scanf("%d%d%d", &n, &k, &m);
	
	for (int i = 1; i <= n; ++i)	f[i][1] = 1;
	for (int i = 2; i <= n; ++i) {
		for (int j = 2; j <= k && j <= i; ++j) {
			f[i][j] = (f[i][j] + f[i - 1][j - 1]) % mod;
			f[i][j] = (f[i][j] + f[i - j][j]) % mod;
		}
	}
	
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		int sum = 0;
		for (int j = 1; i * j <= n && j <= k; ++j)
			sum = (sum + f[n - i * j][k - j]) % mod;
		ans = (ans + 1LL * sum * qpow(i, m) % mod) % mod;
	}
	
	printf("%d\n", ans);
	
	return 0;
}
posted @ 2019-08-28 20:15  四季夏目天下第一  阅读(139)  评论(0编辑  收藏  举报