题意
对于所有大小为(k)的正整数可重集合,要求(sum_{i=1}^ka_i =n)
定义满足上述要求的正整数可重集合的权值为
求所有满足条件的集合的权值之和,模(10^9+7)
其实luogu上有原题,(P4977),那道题数据范围更大,还需要滚动数组优化
解法
由于权值是相加进行计算的,我们可以把每一部分的权值分开计算
比如说对于一个数(a_i)
它在所有的集合中出现的次数之和为(cnt_i)
那么这个数(a_i)做出的贡献就是(a_i^m imes cnt_i)
现在的问题就是如何求出这个(cnt_i)
再次转化一下这个问题,我们可以(O(N))枚举这个数在集合中出现的次数(x),乘上出现次数为(x)的集合个数,累加起来即为最终的答案
现在的问题转化为了求出现次数为(x)的集合个数
设(f[n][k])为权值和为(n),共有(k)个元素的集合个数,那么出现次数至少为(x)的集合个数即为(f[n-x imes 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
", ans);
return 0;
}