容斥做题笔记


P1450 [HAOI2008]硬币购物

\(4\) 种面值的硬币,买 \(n\) 次东西,每次带了 \(d_i\)\(i\) 种硬币,想购买价值为 \(s\) 的东西,请问每次有多少种付款方法。

\(n\leq1000\) , \(d_i,s \leq 10^5\)


显然题目其实就是一个多重背包问题,每次暴力做多重背包,复杂度不可接受。

考虑关键在于对于物品限制让我们没有头绪。

如果不考虑物品限制,那么对于每次购物都是一样的,那就变成了一个完全背包。

考虑上物品限制,容斥一下,减去不合法方案。

也就是对于每个方案选择超过数目的方案。

减去第一个超限,第二个超限,第三个超限,第四个超限。

可以使用容斥原理。

const int N = 2e5;
int n, c[8], d[8], s, f[N];
void solve() {
  for (int i = 1; i <= 4; i++)
    d[i] = read();
  s = read();
  int ans = f[s];
  for (int now = 1; now <= (1 << 4) - 1; now++) {
    int cnt = 0, tmp = s;
    for (int i = 1; i <= 4; i++) {
      if ((now >> (i - 1)) & 1) {
        cnt ^= 1;
        tmp -= (d[i] + 1) * c[i];
      }
    }
    if (tmp >= 0)
      ans += (cnt ? -f[tmp] : f[tmp]);
  }
  write(ans);
  putc('\n');
}
signed main() {
  for (int i = 1; i <= 4; i++)
    c[i] = read();
  n = read();
  f[0] = 1;
  for (int i = 1; i <= 4; i++) {
    for (int j = c[i]; j < N; j++) {
      f[j] += f[j - c[i]];
    }
  }
  while (n--)
    solve();
  flush();
  return 0;
}

P3214 [HNOI2011]卡农

从集合 {\(1,2,3....n\)} 中选择 \(m\) 个互不相同的非空集合且所有元素出现次数为偶数的方案数。

答案对 \(10^8 +7\) 取模。


考虑对元素出现次数没有限制的时候怎么做。

显然就是从所有的子集里面选择 \(m\) 个出来,那么答案其实就是 \(\binom{2^n-1}{m}\)

考虑有限制的情况,我们考虑动态规划。

题目要求的同种音乐你可以不用管他,最后除以 \(m!\) 即可。

1.所有选出的 \(m\)个子集都不能为空。
2.所有选出的 \(m\) 个子集中,不能存在两个完全一样的集合。
3.所有选出的 \(m\) 个子集中,\(1\)\(n\) 每个元素出现的次数必须是偶数。

我们设立 \(dp_i\) 表示到了选到第 \(i\) 个子集的时候,同时满足以上 \(3\) 点的方案数。

考虑得到了 \(dp_{i-1}\) , 显然这个时候的排列数目就是 \(A_{2^n-1}^{i-1}\)

那么考虑去满足条件 \(1\) 与条件 \(2\)

条件 \(1\) 显然就是减去 \(dp_{i-1}\)

考虑条件 \(2\) ,不能有两个完全一样集合。

那么如果把第 \(j\) 个子集和第 \(i\) 个子集去掉,剩下的 \(i?2\) 个子集可以凑成一个合法的方案,即 \(f[i?2]\)

\(j\)\(i?1\) 种取值,子集 \(i\) 的取法为\(2^n?1?(i?2)\) , 即 \(2^n-i+1\)

然后可以写出 \(dp\) 方程,线性递推即可。

const int mod = 1e8 + 7, N = 2e6;
int f[N], a[N], n, m, ans;
int power(int a, int b) {
  int ans = 1;
  while (b) {
    if (b & 1)
      ans = ans * a % mod;
    a = a * a % mod;
    b >>= 1;
  }
  return ans;
}
signed main() {
  n = read(), m = read();
  int tot = power(2, n) - 1;
  a[0] = f[0] = 1;
  for (int i = 1; i <= m; i++)
    a[i] = a[i - 1] * ((tot - i + 1 + mod) % mod) % mod;
  for (int i = 2; i <= m; i++) {
    f[i] = a[i - 1];
    f[i] -= f[i - 1];
    f[i] = (f[i] - f[i - 2] * (tot - i + 2 + mod) % mod * (i - 1) % mod + mod) %
           mod;
  }
  int k = m;
  ans = m;
  while (--k) {
    ans = ans * k % mod;
  }
  printf("%lld\n", f[m] * power(ans, mod - 2) % mod);
  return 0;
}