容斥学习笔记
日志
- 2023/12/6 初步施工。
- 2023/12/8 增加了一些例题、内容。修改了一些错误/模糊的表述。
- 2023/12/10 增加了一些例题。
- 还未完结。
容斥原理
我们有:
即:
还有:
即满足所有条件
容斥原理求交集大小
一般情况下直接求
这里以一些道例题为例。
P1450 [HAOI2008] 硬币购物
题意:共有
种硬币。面值分别为 。某人去商店买东西,去了 次,对于每次购买,他带了 枚 种硬币,想购买 的价值的东西。请问每次有多少种付款方法。
题目要求的是满足
其中
// qwq
#include <bits/stdc++.h>
#define rep(I,A,B) for(int I=(A);I<=(B);I++)
using namespace std;
typedef long long ll;
constexpr int N=2e5+9;
int c[6],d[6],T,n;
ll f[N];
int main(){
f[0]=1;
for(int i=1;i<=4;i++){
cin>>c[i];
for(int j=c[i];j<N;j++)
f[j]+=f[j-c[i]];
}
cin>>T;
while(T--){
for(int i=1;i<=4;i++)
cin>>d[i];
cin>>n;
ll ans=0;
for(int S=0;S<(1<<4);S++){
ll m=n,e=0;
rep(i,1,4)if((S>>(i-1))&1)
m-=(ll)(d[i]+1)*c[i],++e;
if(m<0)continue;
if(e&1)ans-=f[m];
else ans+=f[m];
}
cout<<ans<<'\n';
}
return 0;
}
P3813 [FJOI2017] 矩阵填数
显然答案为所有子矩阵的最大值小于等于
存在一个子矩阵的最大值小于
总时间复杂度为
// qwq
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr ll mo=1e9+7;
ll ksm(ll x,ll y){
ll cur=1;
for(;y;y>>=1,x=x*x%mo)
if(y&1)cur=cur*x%mo;
return cur;
}
ll W,H,n,a[30][30],m,X[30],Y[30],cx,cy;
struct Add{ll x,y,l,r,v;}mat[30];
void solve(){
cin>>H>>W>>m>>n;
for(int i=1;i<=n;i++)
cin>>mat[i].x>>mat[i].l>>
mat[i].y>>mat[i].r>>mat[i].v;
cx=cy=0;
X[++cx]=1,Y[++cy]=1;
X[++cx]=H+1,Y[++cy]=W+1;
for(int i=1;i<=n;i++)
X[++cx]=mat[i].x,X[++cx]=mat[i].y+1,
Y[++cy]=mat[i].l,Y[++cy]=mat[i].r+1;
sort(X+1,X+cx+1),sort(Y+1,Y+cy+1);
cx=unique(X+1,X+cx+1)-X-1;
cy=unique(Y+1,Y+cy+1)-Y-1;
cx--,cy--;
ll ans=0;
for(int S=0;S<(1<<n);S++){
for(int i=1;i<=cx;i++)
for(int j=1;j<=cy;j++)
a[i][j]=m;
ll tmp=1,ci=0;
for(int s=1;s<=n;s++){
ll t=mat[s].v-((S>>(s-1))&1);ci+=((S>>(s-1))&1);
int l=lower_bound(X+1,X+cx+1,mat[s].x)-X,r=lower_bound(X+1,X+cx+1,mat[s].y+1)-X-1;
int x=lower_bound(Y+1,Y+cy+1,mat[s].l)-Y,y=lower_bound(Y+1,Y+cy+1,mat[s].r+1)-Y-1;
for(int i=l;i<=r;i++)for(int j=x;j<=y;j++)a[i][j]=min(a[i][j],t);
}
for(int i=1;i<=cx;i++)for(int j=1;j<=cy;j++){
ll l1=X[i+1]-X[i],l2=Y[j+1]-Y[j];
tmp=(tmp*ksm(a[i][j],l1*l2))%mo;
}
ans=(ans+((ci&1)?mo-1ll:1ll)*tmp%mo)%mo;
// cout<<tmp<<'\n';
}
cout<<ans<<'\n';
return;
}
int main(){
ll t; cin>>t;
while(t--)solve();
return 0;
}
求最大公约数为 k 的数对个数
设
,求 的 的对数。
设
for(int i=1;i<=n;i++){
f[i]=(n/i)*(n/i);
for(int j=i<<1;j<=n;j+=i)
f[i]-=f[j];
}
集合反演
这里仍然以几道例题为例。
P3349 [ZJOI2016] 小星星
题目要求的是树上节点到图中节点的映射方案数,且映射两两不同。设
这个算法的瓶颈是枚举子集的
设
直接子集反演可以得到
// qwq
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N=20;
int lnk[N][N],n,m,p[N],pc;
ll f[N][N];
vector<int>e[N];
void dp(int u,int fa){
for(int i=1;i<=pc;i++)f[u][i]=1;
for(int v:e[u])if(v^fa){
dp(v,u);
for(int i=1;i<=pc;i++)if(f[u][i]){
ll tmp=0;
for(int j=1;j<=pc;j++)
if(lnk[p[i]][p[j]])
tmp+=f[v][j];
f[u][i]=f[u][i]*tmp;
}
}
}
ll sol(int S){
pc=0;
for(int i=1;i<=n;i++)
if((S>>(i-1))&1)
p[++pc]=i;
ll tmp=0; dp(1,0);
for(int i=1;i<=pc;i++)
tmp+=f[1][i];
return tmp*(((n-pc)&1)?-1:1);
}
int main(){
cin>>n>>m;
for(int i=1,x,y;i<=m;i++)
cin>>x>>y,lnk[x][y]=lnk[y][x]=1;
for(int i=1,x,y;i<n;i++)
cin>>x>>y,e[x].push_back(y),
e[y].push_back(x);
ll ans=0;
for(int i=1;i<(1<<n);i++)
ans+=sol(i);
cout<<ans<<'\n';
return 0;
}
P4336 [SHOI2016] 黑暗前的幻想乡
题意简述:有
个建筑公司,每个建筑公司可以修建一部分公路,求每个建筑公司恰好修建一条公路,且修建后图联通的方案数。 。
设
显然
// qwq
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> arr;
typedef vector<arr> Arr;
constexpr int N=67,mo=1e9+7;
inline int Det(Arr a,int n){
int ans=1,rev=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=(a[i][j]+mo)%mo;
for(int i=2;i<=n;i++){
for(int j=i+1;j<=n&&!a[i][i];j++)
if(a[j][i]){a[i].swap(a[j]),rev^=1;}
if(!a[i][i])return 0;
for(int j=i+1;j<=n;j++){
if(a[j][i]>a[i][i])a[i].swap(a[j]),rev^=1;
while(a[j][i]){
int d=a[i][i]/a[j][i];
for(int k=i;k<=n;k++)
a[i][k]=(a[i][k]-(ll)a[j][k]*d%mo+mo)%mo;
a[i].swap(a[j]),rev^=1;
}
}
ans=(ll)ans*a[i][i]%mo;
}
rev&&(ans=(mo-ans)%mo);
return ans;
}
int n;
vector<pair<int,int>>S[N];
int main(){
cin>>n;
for(int i=1,m;i<n;i++){
cin>>m;
for(int j=1,x,y;j<=m;j++)
cin>>x>>y,S[i].emplace_back(x,y);
}
int ans=0;
for(int i=1;i<(1<<n-1);i++){
Arr a(n+1,arr(n+1,0)); int t=0;
for(int j=0;j<n-1;j++)if(i>>j&1){
for(auto k:S[j+1])
a[k.first][k.second]--,a[k.second][k.first]--,
a[k.first][k.first]++,a[k.second][k.second]++;
++t;
}
t=n-1-t;
if(t&1)ans=(ans+mo-Det(a,n))%mo;
else ans=(ans+Det(a,n))%mo;
}
cout<<ans;
return 0;
}
二项式反演
P4859 已经没有什么好害怕的了
给两个数列
, , 要求 两两匹配,使得 的个数减去 的个数等于 ,求总方案数。
先将
有
设
// qwq
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int mo=1e9+9,N=2009;
int a[N],b[N],C[N][N],m,n,f[N][N],fac[N];
int main(){
cin>>n>>m,fac[0]=fac[1]=1;
if((n+m)&1){puts("0");return 0;}
m=n+m>>1;
if(m>n){puts("0");return 0;}
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++)fac[i]=(ll)fac[i-1]*i%mo;
sort(a+1,a+n+1),sort(b+1,b+n+1);
f[0][0]=1;
for(int i=1;i<=n;i++){
int num=lower_bound(b+1,b+n+1,a[i])-b-1;
for(int j=0;j<=n;j++)
f[i][j]=(f[i-1][j]+(j>0?(ll)(num-j+1)*f[i-1][j-1]%mo:0))%mo;
}
for(int i=0;i<=n;i++)
for(int j=C[i][0]=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mo;
int ans=0;
for(int i=m,j=1;i<=n;i++,j=mo-j)
(ans+=(ll)C[i][m]*j%mo*f[n][i]%mo*fac[n-i]%mo)%=mo;
cout<<ans;
return 0;
}
P4491 [HAOI2018] 染色
设
考虑二项式反演:
把式子拆开,ntt 计算即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e7;
const ll mod = 1004535809;
const ll gen = 3;
ll rev[N], w[N];
ll my_pow(ll x, ll y){
ll res = 1;
for(;y;y>>=1,x=x*x%mod)
if(y&1)res=res*x%mod;
return res;
}
ll inv(ll x){return my_pow(x, mod-2);}
void NTT(ll *a, int Len, bool type){
for(int i = 0; i < Len; i++){
rev[i] = (rev[i>>1]>>1) + (i&1?Len>>1:0);
if(rev[i]>i)swap(a[rev[i]],a[i]);
}
for(int d=1; d<Len; d<<=1){
ll W = my_pow(gen, (mod-1)/(d*2));
if(type) W = inv(W);
w[0] = 1;
for(int i = 1; i < d; i++) w[i] = w[i-1] * W % mod;
for(int fir = 0; fir < Len; fir += d<<1){
int sec = fir + d;
for(int i = 0; i < d; i++){
ll a0 = a[fir+i], a1 = a[sec+i] * w[i] % mod;
a[fir+i] = (a0 + a1) % mod;
a[sec+i] = (a0 - a1 + mod) % mod;
}
}
}
if(type){
ll invlen = inv(Len);
for(int i=0; i<Len; i++)
a[i] = a[i] * invlen % mod;
}
}
ll fac[N], ifac[N], a[N], b[N], c[N], n, m, s, mm, ww[N];
void init_fac(ll tt){
fac[0] = ifac[0] = 1;
for(ll i = 1; i <= tt; i++)
fac[i] = 1ll * fac[i-1] * i % mod;
ifac[tt] = inv(fac[tt]);
for(ll i = tt-1; i >= 1; i--)
ifac[i] = 1ll * ifac[i+1] * (i+1) % mod;
}
ll fu1(int x){return (x&1)?-1:1;}
int main(){
cin >> n >> m >> s;
for(int i = 0; i <= m; i++) cin >> ww[i];
init_fac(max(n, m));
mm = min(m, n/s);
for(ll i = 0; i <= mm; i++){
a[i] = (fu1(i) * ifac[i] + mod) % mod;
b[i] = 1ll * fac[m] * ifac[m-i] % mod * my_pow(m-i, n-s*i) % mod *
fac[n] % mod * my_pow(ifac[s], i) % mod * ifac[n-s*i] % mod;
// printf("%lld %lld\n", a[i], b[i]);
}
ll Len = 1;
while(Len <= (mm*2)) Len<<=1;
reverse(b, b+mm+1);
NTT(a, Len, 0);
NTT(b, Len, 0);
for(ll i = 0; i < Len; i++)
a[i] = a[i] * b[i] % mod;
NTT(a, Len, 1);
reverse(a, a+mm+1);
ll ans = 0;
for(ll i = 0; i <= mm; i++)
ans = (ans + ifac[i] * a[i] % mod * ww[i] % mod) % mod;
cout << (ans + mod) % mod << endl;
return 0;
}
P5339 [TJOI2019] 唱、跳、rap和篮球
设
有:
直接
// qwq
#include <bits/stdc++.h>
#define inl inline
using namespace std;
using ll=long long;
constexpr ll mo=998244353,Gen=3,N=1e5+1;
inl ll sub(ll x,ll y){return x-=y,x<0?x+mo:x;}
inl ll suf(ll x,ll y){return x+=y,x>=mo?x-mo:x;}
#define inv(X) ksm((X),mo-2)
inl ll ksm(ll x,ll y){
ll cur=1;
for(;y;y>>=1,x=x*x%mo)
if(y&1)cur=cur*x%mo;
return cur;
}
ll w[N]; int rev[N];
inl void ntt(ll* a,int len,bool ok){
for(int i=0;i<len;i++)
if(rev[i]>i)swap(a[rev[i]],a[i]);
for(int d=1;d<len;d<<=1){
ll W=ksm(Gen,(mo-1)/(d<<1));if(ok)W=inv(W);
w[0]=1;for(int i=1;i<d;i++)w[i]=w[i-1]*W%mo;
for(int fi=0;fi<len;fi+=d<<1){
int se=fi+d;
for(int i=0;i<d;i++){
ll a0=a[fi+i],a1=a[se+i]*w[i]%mo;
a[fi+i]=suf(a0,a1);a[se+i]=sub(a0,a1);
}
}
}
if(ok){
ll iv=inv(len);
for(int i=0;i<len;i++)
a[i]=a[i]*iv%mo;
}
}
ll F[N],G[N];
void mul(ll* a,ll* b,ll n,ll m,ll* c){
int len=1;while(len<=(n+m+2))len<<=1;
for(int i=0;i<len;i++)
rev[i]=(rev[i>>1]>>1)|(i&1?len>>1:0);
for(int i=0;i<len;i++)F[i]=G[i]=0;
for(int i=0;i<=n;i++)F[i]=a[i];
for(int i=0;i<=m;i++)G[i]=b[i];
ntt(F,len,0), ntt(G,len,0);
for(int i=0;i<len;i++)F[i]=F[i]*G[i]%mo;
ntt(F,len,1);
for(int i=0;i<=n+m;i++)c[i]=F[i];
}
ll fac[N],ifac[N];
void init(int n){
fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mo;
ifac[n]=inv(fac[n]);
for(int i=n-1;i;i--)ifac[i]=ifac[i+1]*(i+1)%mo;
}
ll f[4][N];
ll calc(ll A,ll B,ll C,ll D,ll n){
if(A+B+C+D<n)return 0;
for(int i=0;i<=A;i++)f[0][i]=ifac[i];
for(int i=0;i<=B;i++)f[1][i]=ifac[i];
for(int i=0;i<=C;i++)f[2][i]=ifac[i];
for(int i=0;i<=D;i++)f[3][i]=ifac[i];
mul(f[0],f[1],A,B,f[0]);
mul(f[0],f[2],A+B,C,f[0]);
mul(f[0],f[3],A+B+C,D,f[0]);
return f[0][n]*fac[n]%mo;
}
ll C(ll n,ll m){
if(n<0||m<0||n<m)return 0;
return fac[n]*ifac[m]%mo*ifac[n-m]%mo;
}
int main(){
ll n,a,b,c,d,mn,ans=0,nw=1;
cin>>n>>a>>b>>c>>d;
init(max({a,b,c,d,n}));
mn=min({a,b,c,d,n/4});
for(int i=0;i<=mn;i++,nw=sub(mo,nw))
ans=suf(ans,nw*C(n-3*i,i)%mo*calc(a-i,b-i,c-i,d-i,n-4*i)%mo);
cout<<ans<<'\n';
return 0;
}
后记
参考文献:
本文作者:fzrcy
本文链接:https://www.cnblogs.com/fzrcy/p/17880681.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步