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;
}
posted @ 2025-04-13 19:19  长安19路  阅读(19)  评论(0)    收藏  举报