容斥练习合集

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;
}
posted @ 2026-08-03 18:28  AKDreamer_HeXY  阅读(13)  评论(0)    收藏  举报