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;
}

猜你喜欢

转载自www.cnblogs.com/VeniVidiVici/p/11426249.html