P10597 BZOJ4665 小 w 的喜糖 题解
\(\text{P10597 BZOJ4665 小 w 的喜糖 题解}\)
求每个人糖的种类都不同是困难的,不如转化为求没有人相同的方案数。直接计算没有人相同同样不好做,考虑最终的条件很苛刻,考虑用二项式反演来放松限制。具体地,设 \(f_i\) 表示恰好有 \(i\) 个人糖的种类和原来相同,\(g_j\) 表示至少有 \(i\) 个人不变的方案数,那么显然所求为 \(f_0\),且 \(f_i=\sum_{j=i}^n\binom{j}{i}(-1)^{j-i}g_j\)。现在看上去 \(g\) 要好求一些了。
我们设计一个 dp:设 \(dp_{i,j}\) 表示前 \(i\) 种糖果里至少有 \(j\) 个糖果种类不变。那么考虑变化的糖果或是和后面的糖换,或是和前面的糖换,或是不换。我们可以把和后面的糖换的情形先累积到和自己换,再在后面去统计贡献。设 \(c_i\) 表示颜色 \(i\) 的个数,\(s_i\) 表示 \(\sum_{j=1}^i c_j\),那么有:
\[f_{i,j}=\sum_{k=0}^{min(j,c_i)}f_{i-1,j-k}\binom{c_i}{k}\binom{s_i-j}{c_i-k}
\]
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e3 + 5, mod = 1e9 + 9;
int n, a[N];
int num[N], cnt;
int dp[N][N];
int fac[N], inv[N];
int C(int n, int m) {
if (n < m || n < 0 || m < 0) return 0;
return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
void add(int &x, int y) {
x = (x + y) % mod;
}
int qpow(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
signed main() {
fac[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = qpow(fac[N - 1], mod - 2);
for (int i = N - 2; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
if (a[i] != a[i - 1]) ++cnt;
num[cnt]++;
}
dp[0][0] = 1;
int p = 0;
for (int i = 1; i <= cnt; i++) {
p += num[i];
for (int j = 0; j <= p; j++)
for (int k = 0; k <= min(j, num[i]); k++)
add(dp[i][j], dp[i - 1][j - k] * C(num[i], k) % mod * C(p - j, num[i] - k));
int ans = 0;
for (int i = 0; i <= n; i++)
add(ans, ((i & 1) ? mod - 1 : 1) * dp[cnt][i] % mod);
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号