【组合数学】容斥
概念
先容纳全部的情况,然后排斥掉不合法的情况。
对于集合求并问题,我们可以得到:
\[\left| \bigcup_{i=1}^{n}P_i \right| =\sum_{S\subseteq[1,2,\ldots,n]} \left( -1 \right)^{\left| S \right|-1} \left| \bigcap_{s\in S} P_s \right|
\]
例题1
不定方程:\(x_1+x_2+\dots+x_n=S\),每个数都是非负整数,对于每个 \(x_i\),有限制 \(x_i\leq k\),计算不定方程的解数。\(n\leq 10^5,k\leq 10^5\)
枚举至少有几个大于 \(K\),\(S\)就变成了 \(S-i*(k+1)\times i\) 没有限制的问题,插板法即可。
\[ans=\sum_{i=0}^{i\times k\leq S} (-1)^{i} \cdot C_{n}^{i} \cdot C_{S-i\times (k+1)+n-1}^{n-1}
\]
为什么要 \((-1)^i\) 呢?
因为 \(i=1\) 时存在 \((0,3,1)\) 和 \((3,0,1)\) 都变成 \((3,3,1)\),重复计算了,要加上 \(i=2\) 时存在 \((0,0,1)\) 变成 \((3,3,1)\) 的情况。
例题2
下 \(n\) 游戏局,这个人赢 \(m\) 局,最长连胜是 \(k\) 局,如果我们用 \(1\) 表示胜利,用 \(0\) 表示失败,战绩可以看作长度为 \(n\) 的 \(01\) 字符串,两个战绩不同当且仅当两个字符串不同。
限制在赢上,将输的局看作分隔符,赢的局看作变量,此时题目可看作不定方程 \(x_1+x_2+\dots+x_{n-m+1}=m,\max\{{x_i}\}=k\)。
因为必须要有至少一局连胜 \(k\) 局,那么将 \(x_i\leq k\) 的方案数减去 \(x_i\leq k-1\) 的方案数,求法就可以看例题1了。
#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int N=2e5+10;
const int mod=998244353;
int fac[N],inv[N];
int choose(int n,int m){
if(m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int quick(int a,int k){
int base=a%mod;
int ans=1;
while(k){
if(k&1){
ans=(ans*base)%mod;
}
base=(base*base)%mod;
k>>=1;
}
return ans;
}
int n,m,k,s;
signed main(){
ios::sync_with_stdio(0);
cin.tie(nullptr);
fac[0]=1;
for(int i=1;i<=100005;i++){
fac[i]=fac[i-1]*i%mod;
}
inv[100005]=quick(fac[100005],mod-2);
for(int i=100004;i>=0;i--){
inv[i]=inv[i+1]*(i+1)%mod;
}
cin>>n>>s>>k;
if(!(n>=s&&s>=k)){
cout<<0;
return 0;
}
if(k==0){
if(s==0){
cout<<1;
}
else{
cout<<0;
}
return 0;
}
n-=s;
n++;
int ans=0;
for(int i=0;i*k<=s;i++){
int x=choose(n,i)*choose(s-i*(k+1)+n-1,n-1)%mod;
int y=choose(n,i)*choose(s-i*(k+1-1)+n-1,n-1)%mod;
if(i&1){
ans+=y-x;
}
else{
ans+=x-y;
}
ans=(ans%mod+mod)%mod;
}
cout<<ans;
return 0;
}
\[C_{n(k+1)-s-1}^{n-1}
\]