容斥原理和容斥DP
容斥原理
∣ ⋂ i = 1 n S i ‾ ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ∣ = ∑ 0 ≤ k ≤ n ( − 1 ) k ∑ 1 ≤ i 1 < ⋯ < i k ≤ n ∣ ⋂ j = 1 k S i j ∣ \left|\bigcap_{i=1}^n \overline{S_i}\right| = |U| - \left|\bigcup_{i=1}^n S_i\right| = \sum_{0 \le k\le n}(-1)^k\sum_{1\le i_1<\cdots<i_k\le n}\left|\bigcap_{j=1}^k S_{i_j}\right| ∣∣∣∣∣i=1⋂nSi∣∣∣∣∣=∣U∣−∣∣∣∣∣i=1⋃nSi∣∣∣∣∣=0≤k≤n∑(−1)k1≤i1<⋯<ik≤n∑∣∣∣∣∣j=1⋂kSij∣∣∣∣∣
∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ ∣ A ∩ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∪ B ∣ |A \cup B| = |A| + |B| - |A \cap B| \\ |A \cap B| = |A| + |B| - |A \cup B| ∣A∪B∣=∣A∣+∣B∣−∣A∩B∣∣A∩B∣=∣A∣+∣B∣−∣A∪B∣
少的加上,多的减去,简称奇加偶减。
例题
那么怎么办呢?如果没有硬币数量的限制那就多好啊?直接一个完全背包预处理,然后O(1)输出就好了
可是有了硬币的限制怎么办?我们先考虑一个简单一点的情况:只有第一个硬币有限制。
如果我们用类似前缀和的思想(术语叫差分),我们先完全背包预处理好无限制的情况,拿dp[tot]减去dp[tot-c[i]*(d[i]+1)]就是我们所需的方案数。
这是为什么呢?为什么要弄个c[i]*(d[i]+1)?其实我们可以这样想,无限制的情况就是没有那个di,而有限制时,不应该计入答案的方案数就是把c[i]这个硬币取了超过d[i]次,对吧?那么我们手动先取出d[i]+1个c[i]的硬币,然后剩下的价值弄个完全背包,这时就是我们所不需要的答案, 把它减掉就行了。
那么对于4个(或更多)的硬币有限制,我们就逐一把4个硬币单独限制的方案数减掉,这时可能会减重了(即同时两个硬币有限制的情况减了两次),所以我们再把4个硬币两两同时限制的方案数加上,可能又加重了,再把4个硬币33同时限制减掉,最后加上4个同时限制的方案数就是我们所需的答案。这就是大名鼎鼎的容斥原理啊!写成位运算就很优美了!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out1.txt", "w", stdout)
#define MOD 1000000007
#define MAXT 100005
ll C[10];
ll D[10];
ll dp[MAXT];
int main()
{
for (int i = 1; i <= 4; i++)
{
scanf("%lld", C + i);
}
dp[0] = 1;
for (int i = 1; i <= 4; i++)
{
for (int w = C[i]; w < MAXT; w++)
{
dp[w] += dp[w - C[i]];
}
}
int T;
scanf("%d", &T);
while (T--)
{
for (int i = 1; i <= 4; i++)
{
scanf("%lld", D + i);
}
ll S;
scanf("%lld", &S);
ll ans = 0;
for (int b = 0; b <= 15; b++)
{
ll t = S;
int cnt = 0;
for (int i = 0; i <= 3; i++)
{
if ((b >> i) & 1)
{
t -= C[i + 1] * (D[i + 1] + 1);
cnt = 1 - cnt;
}
}
if(t < 0) continue;
if (cnt)
{
ans -= dp[t];
}
else
{
ans += dp[t];
}
}
printf("%lld\n", ans);
}
return 0;
}
选不能超过一半的行,我们枚举超过一半列的情况即可,用总的方案数减去,此题不用容斥原理是因为超过一半列只能有一列,各种情况互不相容,因此直接减即可。
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
#define MOD 998244353
typedef long long ll;
ll dp1[105][4500];
ll dp2[105][105];
ll matrix[105][2005];
ll psum[105];
int n, m;
int main()
{
scanf("%d %d", &n, &m);
for (int r = 1; r <= n; r++)
{
for (int c = 1; c <= m; c++)
{
scanf("%lld", &matrix[r][c]);
matrix[r][c] %= MOD;
psum[r] = (psum[r] + matrix[r][c]) % MOD;
}
}
dp2[0][0] = 1;
for (int r = 1; r <= n; r++)
{
dp2[r][0] = 1;
for (int k = 1; k <= r; k++)
{
dp2[r][k] = (dp2[r - 1][k] + (psum[r] * dp2[r - 1][k - 1]) % MOD) % MOD;
}
}
ll del = 0;
for (int c = 1; c <= m; c++)
{
memset(dp1, 0, sizeof(dp1));
dp1[0][n] = 1;
for (int r = 1; r <= n; r++)
{
for (int j = n - r; j <= n + r; j++)
{
dp1[r][j] = ((
dp1[r - 1][j] +
(((psum[r] - matrix[r][c]) % MOD + MOD) % MOD * dp1[r - 1][j + 1]) % MOD) %
MOD +
(matrix[r][c] * dp1[r - 1][j - 1]) % MOD) %
MOD;
}
}
for (int j = 1; j <= n; j++)
{
del = (del + dp1[n][j + n]) % MOD;
}
}
ll total = 0;
for (int k = 1; k <= n; k++)
{
total = (total + dp2[n][k]) % MOD;
}
printf("%lld", (((total - del) % MOD) + MOD) % MOD);
return 0;
}