8.27 神异之旅
题意
对于所有大小为\(k\)的正整数可重集合,要求\(\sum_{i=1}^ka_i =n\)
定义满足上述要求的正整数可重集合的权值为
求所有满足条件的集合的权值之和,模\(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;
}