容斥原理求解不定方程

前言

在阅读本文前,假设你已经掌握了组合数,集合运算。

容斥原理

设每个物品有 n n n 种不同的特征,具有第 i i i 种特征的物品构成多重集合 S i S_i Si,所有物品构成多重全集 U U U,多重全集 U U U 应包含全部 2 n 2^n 2n 种物品(相当于无视全部特征),但多重全集的大小 ∣ U ∣ |U| U 不一定等于 2 n 2^n 2n,因为每种物品的具体个数是未知的。

常用公式

  1. 至少具有一个特征(具有该特征的并集)

∣ ⋃ i = 1 n S i ∣ = ∑ i ∣ S i ∣ − ∑ i < j ∣ S i ∩ S j ∣ + ∑ i < j < k ∣ S i ∩ S j ∩ S k ∣ − ⋯ \left | \bigcup_{i=1}^{n}S_i \right | =\sum_{i}^{}\left | S_i \right | - \sum_{i < j}^{}\left | S_i \cap S_j \right | +\sum_{i< j< k}^{}\left | S_i\cap S_j\cap S_k \right | - \cdots i=1nSi =iSii<jSiSj+i<j<kSiSjSk

  1. 至少不具有一个特征(不具有该特征的并集)等价于至少具有一个相反特征

∣ ⋃ i = 1 n S i ‾ ∣ = ∑ i ∣ S i ‾ ∣ − ∑ i < j ∣ S i ‾ ∩ S j ‾ ∣ + ∑ i < j < k ∣ S i ‾ ∩ S j ‾ ∩ S k ‾ ∣ − ⋯ \left | \bigcup_{i=1}^{n}\overline{S_i} \right | =\sum_{i}^{}\left | \overline{S_i} \right | - \sum_{i < j}^{}\left | \overline{S_i} \cap \overline{S_j} \right | +\sum_{i< j< k}^{}\left | \overline{S_i}\cap \overline{S_j}\cap \overline{S_k} \right | - \cdots i=1nSi =i Si i<j SiSj +i<j<k SiSjSk

  1. 具有全部特征(全集-至少不具有一个特征)

∣ ⋂ i = 1 n S i ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ‾ ∣ \left | \bigcap_{i=1}^{n}S_i \right | = \left | U \right | - \left | \bigcup_{i=1}^{n}\overline{S_i} \right | i=1nSi =U i=1nSi

  1. 不具有任何特征(全集-至少具有一个特征)

∣ ⋂ i = 1 n S i ‾ ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ∣ \left | \bigcap_{i=1}^{n}\overline{S_i} \right | = \left | U \right | - \left | \bigcup_{i=1}^{n}S_i \right | i=1nSi =U i=1nSi

插板法

  1. n n n 个完全相同的元素分成 k k k 组,每组至少有一个元素的方案数。

原问题等价于将 k − 1 k-1 k1 块板插入到 n − 1 n-1 n1 个间隔中,方案数为:

( n − 1 k − 1 ) \binom{n-1}{k-1} (k1n1)

  1. n n n 个完全相同的元素分成 k k k 组,每组可以为空的方案数。

我们先补 k − 1 k-1 k1 个完全相同的元素,则原问题变成从 n + k − 1 n+k-1 n+k1 个完全相同的元素中选 k − 1 k-1 k1 个元素作为板的方案数:

( n + k − 1 k − 1 ) \binom{n+k-1}{k-1} (k1n+k1)

求解不定方程

  1. 给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m,其中 m ≥ n m\ge n mn,求正整数解的个数。

原问题等价于(插板法问题 1 1 1)把 m m m 个完全相同的元素分成 n n n 组,每组至少有一个元素的方案数,所以解的个数为:

( m − 1 n − 1 ) \binom{m-1}{n-1} (n1m1)

  1. 给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m,其中 m ≥ 0 m\ge 0 m0,求非负整数解的个数。

原问题等价于(插板法问题 2 2 2)把 m m m 个完全相同的元素分成 n n n 组,每组可以为空的方案数,所以解的个数为:

( m + n − 1 n − 1 ) \binom{m+n-1}{n-1} (n1m+n1)

  1. 给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m n n n 个限制 x i ≥ b i x_i\ge b_i xibi,其中 m ≥ 0 , b i ≥ 0 , ∑ i = 1 n b i ≤ m m\ge 0,b_i\ge 0,\sum_{i=1}^{n}b_i\le m m0,bi0,i=1nbim,求非负整数解的个数。

原问题等价于把 m m m 个完全相同的元素分成 n n n 组,第 i i i 组至少有 b i b_i bi 个元素的方案数。

对于所有 1 ≤ i ≤ n 1\le i\le n 1in,我们先给第 i i i 组放 b i b_i bi 个元素,那么问题变成(求解不定方程问题 2 2 2)把 m − ∑ i = 1 n b i m-\sum_{i=1}^{n}b_i mi=1nbi 个完全相同的元素分成 n n n 组,每组可以为空的方案数,所以解的个数为:

( m − ∑ i = 1 n b i + n − 1 n − 1 ) \binom{m-\sum_{i=1}^{n}b_i+n-1}{n-1} (n1mi=1nbi+n1)

  1. 给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m n n n 个限制 x i ≤ b i x_i\le b_i xibi,其中 m ≥ 0 , b i ≥ 0 m\ge 0,b_i\ge 0 m0,bi0,求非负整数解的个数。

上面写了这么多,其实都是在给这题铺垫。

先重申一下定义,将不定方程的解视为物品,将限制视为特征,若一个不定方程的解满足第 i i i 个限制(即 x i ≤ b i x_i\le b_i xibi),我们就说一个物品具有第 i i i 个特征。

原问题等价于求具有全部特征的物品数,我们在这里使用容斥原理第 3 3 3 个公式:

∣ ⋂ i = 1 n S i ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ‾ ∣ \left | \bigcap_{i=1}^{n}S_i \right | = \left | U \right | - \left | \bigcup_{i=1}^{n}\overline{S_i} \right | i=1nSi =U i=1nSi

全集 ∣ U ∣ |U| U 表示无视全部特征的方案数,即(求解不定方程问题 2 2 2)求不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m 的非负整数解的个数,所以解的个数为:

( m + n − 1 n − 1 ) \binom{m+n-1}{n-1} (n1m+n1)

∣ ⋃ i = 1 n S i ‾ ∣ \left | \bigcup_{i=1}^{n}\overline{S_i} \right | i=1nSi 表示至少不具有一个特征的方案数等价于至少具有一个相反特征的方案数,所以问题变成:

给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m n n n 个限制 x i ≥ b i + 1 x_i\ge b_i + 1 xibi+1 x i ≤ b i x_i\le b_i xibi 的相反限制),求至少满足一个限制的解的个数。

这里我们使用容斥原理的第 2 2 2 个公式:

∣ ⋃ i = 1 n S i ‾ ∣ = ∑ i ∣ S i ‾ ∣ − ∑ i < j ∣ S i ‾ ∩ S j ‾ ∣ + ∑ i < j < k ∣ S i ‾ ∩ S j ‾ ∩ S k ‾ ∣ − ⋯ \left | \bigcup_{i=1}^{n}\overline{S_i} \right | =\sum_{i}^{}\left | \overline{S_i} \right | - \sum_{i < j}^{}\left | \overline{S_i} \cap \overline{S_j} \right | +\sum_{i< j< k}^{}\left | \overline{S_i}\cap \overline{S_j}\cap \overline{S_k} \right | - \cdots i=1nSi =i Si i<j SiSj +i<j<k SiSjSk

为了求 ∣ ⋃ i = 1 n S i ‾ ∣ \left | \bigcup_{i=1}^{n}\overline{S_i} \right | i=1nSi ,我们需要暴力枚举所有集合,集合包含奇数个限制是正数,偶数个限制是负数。

我们从上面的公式中任意取出一项 ∣ S i ‾ ∩ S j ‾ ∩ S k ‾ ∣ \left | \overline{S_i}\cap \overline{S_j}\cap \overline{S_k} \right | SiSjSk ,这项表示同时满足 i , j , k i,j,k i,j,k 3 3 3 个限制的解的数量,问题变成(求解不定方程问题 3 3 3)给出不定方程 ∑ i = 1 n x i = m \sum_{i=1}^{n}x_i=m i=1nxi=m 3 3 3 个限制 x i ≥ b i + 1 , x j ≥ b j + 1 , x k ≥ b k + 1 x_i\ge b_i+1,x_j\ge b_j+1,x_k\ge b_k+1 xibi+1,xjbj+1,xkbk+1,求满足全部限制的解的个数,其他项同理,所以方案数为:

( m − ( b i + 1 ) − ( b j + 1 ) − ( b k + 1 ) + n − 1 n − 1 ) \binom{m-(b_i+1)-(b_j+1)-(b_k+1)+n-1}{n-1} (n1m(bi+1)(bj+1)(bk+1)+n1)

总公式为:

∣ ⋂ i = 1 n S i ∣ = ( m + n − 1 n − 1 ) − ( ∑ i ∣ S i ‾ ∣ − ∑ i < j ∣ S i ‾ ∩ S j ‾ ∣ + ∑ i < j < k ∣ S i ‾ ∩ S j ‾ ∩ S k ‾ ∣ − ⋯   ) \left | \bigcap_{i=1}^{n}S_i \right | =\binom{m+n-1}{n-1}-(\sum_{i}^{}\left | \overline{S_i} \right | - \sum_{i < j}^{}\left | \overline{S_i} \cap \overline{S_j} \right | +\sum_{i< j< k}^{}\left | \overline{S_i}\cap \overline{S_j}\cap \overline{S_k} \right | - \cdots) i=1nSi =(n1m+n1)(i Si i<j SiSj +i<j<k SiSjSk )

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define pii pair<int, int>
#define all(x) (x).begin(), (x).end()
#define endl '\n'
// #define endl " line in : " << __LINE__ << endl
ostream& operator << (ostream &out, const pii &p) {
  return out << '{' << p.first << ',' << p.second << '}';
}
const int N = 605, INF = 1e17, P = 998244353;
int m, n, b[N];
int ksm(int a, int b) {
  assert(0 <= a && a < P && b >= 0);
  int x = 1;
  while (b) {
    if (b & 1) x = x * a % P;
    a = a * a % P;
    b >>= 1;
  }
  return x;
}
struct {
  int n;
  vector<int> fac, inv;
  void init(int n) {
    assert(n >= 0);
    this->n = n;
    fac.assign(n + 5, 0);
    inv.assign(n + 5, 0);
    fac[0] = inv[0] = 1;
    for (int i = 1; i <= n; i++) {
      fac[i] = fac[i - 1] * i % P;
      inv[i] = inv[i - 1] * ksm(i, P - 2) % P;
    }
  }
  int operator()(int n, int m) {
    if (n < 0 || m < 0 || m > n)
      return 0;
    assert(n <= this->n && m <= this->n);
    return fac[n] * inv[m] % P * inv[n - m] % P;
  }
} C;
void test() {
  cin >> m >> n;
  for (int i = 0; i < n; i++)
    cin >> b[i];
  C.init(n + m + 5);
  int sum = 0;
  // 暴力枚举所有集合
  for (int i = 1; i < (1 << n); i++) {
    int m2 = m;
    // 去掉限制
    for (int j = 0; j < n; j++)
      if (i >> j & 1)
        m2 -= b[j] + 1;
    // 注意正负号
    sum += (popcount((uint)i) & 1 ? 1 : -1) * C(m2 + n - 1, n - 1);
  }
  cout << C(m + n - 1, n - 1) - sum << endl;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  // int T; cin >> T; while (T--)
  test();
  return 0;
}

例题

暂时找不到合适的例题。 (是我太懒了)

对拍

这里我只能提供一份对拍代码。

ans.cpp

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define all(x) (x).begin(), (x).end()
#define endl '\n'
// #define endl " line in : " << __LINE__ << endl
ostream& operator << (ostream &out, const pii &p) {
  return out << '{' << p.first << ',' << p.second << '}';
}
const int N = 2e5 + 5, INF = 1e17, P = 998244353;
int m, n, b[N];
int ans;
void dfs(int x, int sum) {
  if (x == n + 1) {
    if (sum == m)
      ans++;
    return;
  }
  for (int i = 0; i <= b[x]; i++)
    dfs(x + 1, sum + i);
}
void test() {
  cin >> m >> n;
  for (int i = 1; i <= n; i++)
    cin >> b[i];
  dfs(1, 0);
  cout << ans << endl;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  // int T; cin >> T; while (T--)
  test();
  return 0;
}

chk.cpp

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define pii pair<int, int>
#define all(x) (x).begin(), (x).end()
// #define endl '\n'
// #define endl " line in : " << __LINE__ << endl
ostream& operator << (ostream &out, const pii &p) {
  return out << '{' << p.first << ',' << p.second << '}';
}
const int N = 2e5 + 5, INF = 1e17, P = 998244353;
mt19937_64 gen(time(0));
uint rand(uint l, uint r) {
  uniform_int_distribution<uint> dis(l, r);
  return dis(gen);
}
// 0 <= m <= 30, n = 6, 0 <= b_i <= 5
void create_input() {
  ofstream f("in.txt");
  int m = rand(0, 30), n = 6;
  f << m << ' ' << n << endl;
  for (int i = 1; i <= 6; i++)
    f << rand(0, 5) << ' ';
  f << endl;
  f.close();
}
int check() {
  ifstream ans("ans.txt"), test("test.txt");
  while (1) {
    int x = ans.get(), y = test.get();
    if (x != y) {
      ans.close(), test.close();
      return 1;
    }
    if (x == -1) {
      ans.close(), test.close();
      return 0;
    }
  }
}
void test() {
  for (int cnt = 1; ; cnt++) {
    create_input();
    system("ans.exe < in.txt > ans.txt");
    system("test.exe < in.txt > test.txt");
    if (check()) {
      cout << "WA" << endl;
      break;
    }
    if (cnt % 10 == 0)
      cout << cnt << endl;
  }
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  // int T; cin >> T; while (T--)
  test();
  return 0;
}

感谢你阅读本文,请收下只可爱的刻晴。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值