CF 961G Partitions

推不动式子

我们考虑每一个$w_i$对答案的贡献,因为题目中定义集合的价值为$W(S) = \left | S \right |\sum_{x \in S}w_x$,这个系数$\left | S \right |$可以看作集合中所有的元素(包括$i$自己)对$i$产生了一次贡献,那么我们考虑一个元素$j$对$i$的贡献:

1、$j == i$的时候,相当于求把$n$个小球放到$k$个盒子里面的方案数,为$S(n, k)$($S$表示第二类斯特林数)。

2、$j \neq i$的时候,只有$j$和$i$放在同一个集合里面才能产生贡献,为$S(n - 1, k)$。

然后代入第二类斯特林数的通项公式直接算就好了。

最后的答案就是

$$(S(n, k) + (n - 1)S(n - 1, k)) * \sum_{i = 1}^{n}w_i$$

时间复杂度$O(klogk)$。

另外一种高能的推法:传送门

Code:

#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;
const ll P = 1e9 + 7;

ll sum = 0, fac[N], inv[N];

inline ll fpow(ll x, ll y) {
    ll res = 1LL;
    for (; y > 0; y >>= 1) {
        if (y & 1) res = res * x % P;
        x = x * x % P;
    }
    return res;
}

inline ll getS(int n, int k) {
    ll res = 0;
    for (int i = 0; i <= k; i++) {
        ll opt = i & 1 ? -1LL : 1LL;
        res = (res + opt * inv[i] % P * fpow(k - i, n) % P * inv[k - i] % P + P) % P;
    }
    return res;
}

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        ll now; scanf("%lld", &now);
        sum = (sum + now) % P;
    }
    
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P;
    inv[n] = fpow(fac[n], P - 2);
    for (int i = n - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % P;
    
    ll ans = (getS(n, k) + (n - 1) * getS(n - 1, k) % P) % P;
    ans = ans * sum % P;
    printf("%lld\n", ans);
    
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/CzxingcHen/p/10204511.html