容斥练习合集
QOJ-15189 Introversion
题意
一个长度为 \(2n\) 的数组,\(1\sim n\) 每个数需出现 \(2\) 次,其中有一些位置已经确定。求不存在相邻两个数相同的放置方案数,对 \(10^9+7\) 取模。
\(2 \le n\le 100\)。
思路
很容易想到容斥,对于一种合法的放置方案,我们可以提前钦定有多少相邻对 \((i,i+1)\) 是满足 \(a_i=a_{i+1}\) 的。答案为 \(\displaystyle \sum_{S\subseteq 所有可行相邻对}(-1)^{|S|}\times N(S)\),其中 \(N(S)\) 表示钦定集合 \(S\) 中的相邻对,对应的放置方案数为多少。(注意,钦定指的是至少包含这些,但不一定只包含这些)。
首先,输入的每个位置,要么是空位,要么是半固定的(\(x_i\) 只出现一次),要么是全固定的(\(x_i\) 出现两次)。
我们设 \(A\) 表示 \(1\sim n\) 中从未出现过的数的个数,\(B\) 表示出现一次的个数。
考虑这些相邻对 \((i, i+1)\) 可能有哪几种情况:
- 两个均为空位(称为类型一);
- 一个为空位,一个为半固定的(称为类型二)。
那么显然这个全固定的是不会参与到可行相邻对里面的。
在一个合法的相邻对集合 \(S\) 当中,若其中有 \(x\) 个类型一的对,\(y\) 个类型二的对,考虑如何计算 \(N(S)\):
-
已知的这个类型一的对,可以从 \(A\) 种数中任选 \(x\) 种并排列,方案数为 \(\textbf{A}_A^x=\dfrac{A!}{(A-x)!}\)。
-
还剩余 \(q=2A+B-2x-y\) 个位置没有确定放什么数字。
-
剩下的数中,有 \(A-x\) 种数字是出现两次的,两个数字交换后不影响结果,所以,这个 \(q\) 个东西的排列方案数为 \(\dfrac{q!}{2^{A-x}}\)。
-
因此,\(N(S)= \dfrac{A!}{(A-x)!}\times \dfrac{q!}{2^{A-x}}\)。
答案变为 \(\displaystyle \sum_S (-1)^{x+y}\times \dfrac{A!}{(A-x)!}\times \dfrac{q!}{2^{A-x}}\)。
此时我们惊讶地发现,这个结果只跟 \(x,y\) 有关(因为 \(q\) 是关于 \(x,y\) 的代数式,而且 \(A,B\) 是恒定的),那我们可以把所有 \(x,y\) 相同的集合合到一起去算,答案即为 \(\displaystyle \sum_{x,y} cnt(x,y)\times (-1)^{x+y}\times \dfrac{A!}{(A-x)!}\times \dfrac{q!}{2^{A-x}}\),其中 \(cnt(x,y)\) 表示有 \(x\) 个类型一的对,\(y\) 个类型二的对的集合 \(S\) 有多少个。
现在问题就转化成了,对于已经给定数组 \(a\),如何求 \(cnt(x, y)\)。
考虑 dp,设 \(f(i,x,y,0/1)\) 表示,只考虑到前 \(i\) 个位置,已经出现了 \(x\) 个类型一,\(y\) 个类型二,且 \((i - 1,i)\notin S\) / \((i - 1,i) \in S\) 的集合 \(S\) 的数量。
初始状态 \(f(1,0,0,0)=1,f(1,0,0,1)=0\)。
转移的时候,对于 \(i, x, y\):
-
如果让 \((i, i + 1)\) 不加入集合,那么前一个可以加也可以不加,将 \(f(i + 1,x,y,0)\) 加上 \(f(i,x,y,0)+f(i,x,y,1)\)。
-
如果让 \((i, i+1)\) 加入集合,前一个必须不加:
- 若 \(a_i,a_{i+1}\) 均为空位,可以放一个类型一的对进去,将 \(f(i+1,x+1,y,1)\) 加上 \(f(i,x,y,0)\)。
- 若 \(a_i,a_{i+1}\) 其中一个为空位,其中一个是半固定的,可以放一个类型二的对进去,将 \(f(i+1,x,y+1,1)\) 加上 \(f(i,x,y,0)\)。
- 其他情况不可转移。
最终,对于所有 \(x,y\),\(cnt(x,y)\) 即为 \(f(2n,x,y,0)+f(2n,x,y,1)\)。
前面的系数计算只要预处理出阶乘、阶乘逆元和 \(2\) 的幂次逆元取模后的表即可。
整体时间复杂度 \(\mathcal{O}(n^3)\),dp 数组可以滚动优化掉第一维但是代码里没有这么做。
#define int long long
int a[N << 1], cnt[N], type[N << 1];
int f[N << 1][N][N][2];
int ipw[N << 1], fac[N << 1], inv[N << 1];
int qpow(int a, int b){
int res = 1; a %= MOD;
while (b){
if (b & 1ll) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
void init(int m){
fac[0] = 1;
for (int i = 1; i <= m; i++) fac[i] = fac[i - 1] * i % MOD;
inv[m] = qpow(fac[m], MOD - 2);
for (int i = m - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % MOD;
ipw[0] = 1, ipw[1] = inv[2];
for (int i = 2; i <= m; i++) ipw[i] = ipw[i - 1] * ipw[1] % MOD;
}
void solve(){
int n; cin >> n;
for (int i = 1; i <= n; i++) cnt[i] = 0;
memset(f, 0, sizeof(f));
for (int i = 1; i <= 2 * n; i++) cin >> a[i], cnt[a[i]]++;
int A = 0, B = 0;
for (int i = 1; i <= n; i++){
if (cnt[i] == 0) A++;
else if (cnt[i] == 1) B++;
}
for (int i = 1; i <= 2 * n; i++){ // type[i] 0 表示空位,1 表示半固定,2 表示全固定
if (a[i] == 0) type[i] = 0;
else type[i] = cnt[a[i]];
}
// dp 计算 cnt(x, y) 的值
f[1][0][0][0] = 1;
for (int i = 1; i < 2 * n; i++){
for (int x = 0; x <= A; x++){
for (int y = 0; y <= B; y++){
f[i + 1][x][y][0] = (f[i + 1][x][y][0] + f[i][x][y][0] + f[i][x][y][1]) % MOD;
if (type[i] == 0 && type[i + 1] == 1 || type[i] == 1 && type[i + 1] == 0){
if (y < B) f[i + 1][x][y + 1][1] = (f[i + 1][x][y + 1][1] + f[i][x][y][0]) % MOD;
} else if (type[i] == 0 && type[i + 1] == 0){
if (x < A) f[i + 1][x + 1][y][1] = (f[i + 1][x + 1][y][1] + f[i][x][y][0]) % MOD;
}
}
}
}
int ans = 0;
for (int x = 0; x <= A; x++){
for (int y = 0; y <= B; y++){
int coef = (f[2 * n][x][y][0] + f[2 * n][x][y][1]) % MOD;
int lft = 2 * A + B - 2 * x - y;
int nd = fac[A] * inv[A - x] % MOD, ns = fac[lft] * ipw[A - x] % MOD;
if ((x + y) & 1) ans = (ans - coef * nd % MOD * ns % MOD) % MOD;
else ans = (ans + coef * nd % MOD * ns % MOD) % MOD;
}
}
cout << (ans + MOD) % MOD << endl;
}
main(){
init(200);
int T; cin >> T; while (T--) solve();
return 0;
}
P2768 珍珠项链
题意
有 \(k\) 种珠子用来串成项链,要求每种珠子至少用一次,问串成长度为 \([1,n]\) 的项链共有多少种方案。
思路
题目要求对于一种合法方案,每种珠子都要用到,这个不是很好做,考虑容斥。
我们可以自行钦定有几个颜色是不用的,剩下的随意。枚举项链长度,发现这个答案的式子就是:\(\displaystyle \sum_{i=1}^n \sum_{j=0}^k(-1)^{j}\binom{k}{j}(k-j)^i\)。
这样复杂度是 \(\mathcal{O}(nk)\) 的,无法通过,可以考虑交换求和顺序,看看里面的能不能合起来算,变成 \(\displaystyle \sum_{j=0}^k\sum_{i=1}^n(-1)^j\binom{k}{j}(k-j)^i\),即 \(\displaystyle \sum_{j=0}^k(-1)^j\binom{k}{j}\sum_{i=1}^n(k-j)^i\)。
这个最右边的 \(\displaystyle \sum_{i=1}^n(k-j)^i\) 就是一个等比数列求和,即 \(\dfrac{(k-j)^{n+1}-(k-j)}{(k-j)-1}\)。当然,\(k-j=1\) 时它等于 \(n\),需要特判。
提前预处理组合数,这样对于每个 \(k\) 可以 \(\mathcal{O}(1)\) 计算方案数,总复杂度 \(\mathcal{O}(k)\)。
void solve(){
int n, k; cin >> n >> k;
int ans = 0;
for (int j = 0; j <= k; j++){
int coef = C(k, j);
int num = 0;
if (k - j == 1) num = n;
else num = (qpow(k - j, n + 1) - (k - j)) % MOD * qpow(k - j - 1, MOD - 2) % MOD;
if (j & 1) ans = (ans - coef * num % MOD) % MOD;
else ans = (ans + coef * num % MOD) % MOD;
}
cout << (ans + MOD) % MOD << endl;
}

浙公网安备 33010602011771号