容斥原理
$\mathbf{Part}$ $\mathbb{A}$ 在背包上的应用 :
例题 [HAOI2008] 硬币购物
题目描述
共有 $4$ 种硬币。面值分别为 $c_1,c_2,c_3,c_4$。
某人去商店买东西,去了 $n$ 次,对于每次购买,他带了 $d_i$ 枚 $i$ 种硬币,想购买 $s$ 的价值的东西。请问每次有多少种付款方法。
- 对于 $100\%$ 的数据,保证 $1 \leq c_i, d_i, s \leq 10^5$,$1 \leq n \leq 1000$。
朴素多重背包是 $O(n^2m)$,单调队列优化后为 $O(nm)$。但是都无法通过。
因为没有限制的问题是 $O(n)$ 完全背包,我们考虑逆向思维,先算总方案数,减去不合法的。
考虑简化问题:只有第 $i$ 个硬币有限制。这时方案数是 $f[s-c_i \times (d_i +1)]$,我们强制选择 $d_i+1$ 个硬币 $i$ 后,不管选多少个都是不合法的。然后仿照容斥的思路就可以了。  
#include<bits/stdc++.h>
using namespace std;
#define R(i, a, b) for(int i = a;i <= b;i++)
typedef long long ll;
const int N = 1e5 + 5;
int n, c[5], d[5], s;
ll f[N], ans;
int main(){
    scanf("%d %d %d %d %d", &c[1], &c[2], &c[3], &c[4], &n); f[0] = 1;
    R(i, 1, 4) R(s, c[i], N - 1) f[s] += f[s - c[i]];
    R(i, 1, n) {
        scanf("%d %d %d %d %d", &d[1], &d[2], &d[3], &d[4], &s); ans = 0; 
        R(i, 0, 15) {
            ll t = s, k = 1;
            R(j, 1, 4) if((i >> (j - 1)) & 1) t -= c[j] * (d[j] + 1), k *= -1;
            if(t >= 0) ans += k * f[t]; 
        }
        printf("%lld\n", ans);
    }
    return 0;
}$\mathbf{Part}$ $\mathbb{B}$ 一般计数题 :
例题 [CSP-S2019] Emiya 家今天的饭
题目描述
你有一个 $n \times m$ 的网格,每个格子有 $a_{i,j}$ 个可以选取的点。每次最多在每一行选取一个点。设当前已经选取了 $k$ 个点,则选取同一列的点的数量不能超过 $\left \lfloor \frac{k}{2} \right \rfloor $。且不允许不选点,即 $k \ge 1$。
对于所有测试点,保证 $1 \leq n \leq 100$,$1 \leq m \leq 2000$,$0 \leq a_{i,j} \lt 998,244,353$。
直接做比较困难,不妨转化为求不合法方案数。不考虑第二个限制的方案数为 $\prod_{i = 1}^{n} (\sum_{j=1}^{m} a_{i,j}+1) - 1 $,我们只需求出不合法方案数。
设 $f_{i,j,k}$  表示前 $i$ 行在第 $c$ 列选了 $j$ 个点,在其他列选了 $k$ 个点,令 $s_i = \sum_{j=1}^{m}a_{i,j}$ 有:$$f_{i,j,k} = f_{i-1,j,k}+f_{i-1,j-1,k} \times a_{i,c} +f_{i-1,j,k-1} \times (s_i - a_{i,c})$$
转移是 $O(n^3)$ 的,由于要枚举每一列,总复杂度为 $O(n^3m)$
考虑减去无用状态,发现我们并不关心 $j$ 和 $k$ 的具体数值,我们只要知道 $j$ 和 $k$ 的相对大小,即 $j-k$ 的符号,就可以进行转移了。设 $f_{i,t}$ ,其中 $t$ 表示 $j-k$,有:$$f_{i,t} = f_{i-1,t}+f_{i-1,t-1} \times a_{i,c} +f_{i-1,t+1} \times (s_i - a_{i,c})$$
总复杂度是 $O(n^2m)$ 的,实现时注意 $t$ 可以为负数。
 #include<bits/stdc++.h>
using namespace std;
const int N = 105, M = 2005;
typedef long long ll;
const ll Mo = 998244353;
int a[N][M], n, m;
ll f[N][N << 1], s[N], ans = 1;   
int main() {
    scanf("%d %d", &n, &m); 
    for(int i =1 ;i <= n;i++) {
        for(int j =1;j <= m;j++) 
            scanf("%d", &a[i][j]), s[i] = (s[i] + a[i][j]) % Mo;
        ans = ans * (s[i] + 1) % Mo;
    } ans--;
    for(int i = 1;i <= m;i++) {
        memset(f, 0, sizeof f);
        f[0][n] = 1;
        for(int in = 1;in <= n;in++) {
            for(int j = n - in;j <= n + in;j++) 
                f[in][j] = (f[in - 1][j] + f[in - 1][j - 1] * a[in][i] % Mo + f[in - 1][j + 1] * (s[in] + Mo - a[in][i]) % Mo ) % Mo;
        }
        for(int j = 1;j <= n;j++) ans = (ans + Mo - f[n][n + j]) % Mo;
    }
    printf("%lld", (ans % Mo + Mo) % Mo);
    return 0;
} 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号