文章目录
- 什么是反演?
- 正题:二项式反演
- 例题
- 1.[BZOJ 4487: [Jsoi2015]染色问题](http://www.lydsy.com/JudgeOnline/problem.php?id=4487)
- 2.[CF1228E Another Filling the Grid](https://www.luogu.com.cn/problem/CF1228E)
- 3.[Luogu P4859 已经没有什么好害怕的了](https://www.luogu.com.cn/problem/P4859)
- 4.[BZOJ 2839 集合计数](https://darkbzoj.tk/problem/2839)
- 5.[BZOJ 4710: [Jsoi2011]分特产](http://www.lydsy.com/JudgeOnline/problem.php?id=4710)
- 6.[CF285E Positions in Permutations](https://www.luogu.com.cn/problem/CF285E)
- 7.[Luogu P4491 [HAOI2018]染色](https://www.luogu.com.cn/problem/P4491)
什么是反演?
类似这样 ,现在已知 ,要你求 .这就是一个反演,即一个反推的过程.
假设 .
这显然 ,得到单位矩阵,也就是满足 .
即求和套反演为自身.
一些反演:莫比乌斯反演,二项式反演…(其实我就会这俩)
正题:二项式反演
二项式反演有三种形式:
- (极强的对称性)
下面证明第一条,其他的证明类似,读者自证不难.
由于:
所以:
证毕!
应用方法:
当有
的话用第一条.
用第二三条的时候,即"至多"/"至少"量比"正好"量更好计算时.
应用题可以戳最上面的链接看.
例题
1.BZOJ 4487: [Jsoi2015]染色问题
三维容斥-----对二项式定理的推广.
设
表示
的方案数.
定义
表示
的方案数.
则有:
反演:
可理解为对每一维依次反演.
显然暴力需要:
的复杂度.这样会T^T.
如果预处理幂的话也是
的,依然过不了.
观察式子可以发现有幂次和组合数,我们尝试用二项式定理进行合并.
即
预处理幂->
.
计算复杂度->
.
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=410,size=1<<20,mod=1e9+7;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m,c,p[N][N];
ll jc[N],inv[N],ans;
ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}
ll power(ll a,ll b=mod-2) {
ll c=1;
while(b) {
if(b&1) c=c*a%mod;
b=b/2; a=a*a%mod;
}
return c;
}
int main() {
qr(n); qr(m); qr(c);
jc[0]=inv[0]=1; for(int i=1;i<=400;i++) inv[i]=power(jc[i]=jc[i-1]*i%mod);
for(int i=1;i<=c+1;i++)
for(int j=p[i][0]=1;j<=m;j++)
p[i][j]=(ll)p[i][j-1]*i%mod;
for(int j=1,fj=(m&1)?1:-1;j<=m;j++,fj=-fj)
for(int k=0,fk=((c&1)?-1:1)*fj;k<=c;k++,fk=-fk)
(ans += fk*C(m,j)*C(c,k)%mod*power(p[k+1][j]-1,n)%mod) %= mod;
pr2((ans+mod)%mod);
return 0;
}/*
二项式反演的三维形式.
g[i][j][k]表示至多i行,j列,k钟颜色的方案数.(k+1)^(i*j)
总复杂度O(n^2 log(n))
*/
2.CF1228E Another Filling the Grid
题意:给你个 的矩阵,每个格子可填 ,求每行每列的最小值为1的方案数.
可以发现 的每个数是无区别的.
定义状态 ,
即为答案.
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=255,size=1<<20,mod=1e9+7;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m;
ll ans,C[N][N];
ll power(ll a,ll b) {
ll c=1;
while(b) {
if(b&1) c=c*a%mod;
b/=2; a=a*a%mod;
}
return c;
}
int main() {
qr(n); qr(m);
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
for(int i=0;i<=n;i++)
for(int j=0,k;j<=n;j++) {
k=n*(i+j)-i*j;
ans+=((i+j)&1?-1:1)*C[n][i]*C[n][j]%mod*power(m-1,k)%mod*power(m,n*n-k)%mod;
}
pr2((ans%mod+mod)%mod);
return 0;
}
3.Luogu P4859 已经没有什么好害怕的了
因为输入的两个数组( )的数互不相等,所以可得 (以下令 ).
设 分别表示正好和至少为 的方案数.
我们设法构造
把两个数组都排序,定义
.
所以:
然后,反演即可.
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=2010,size=1<<20,mod=1e9+9;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m,a[N],b[N],f[N],jc[N],inv[N];
int main() {
qr(n); qr(m); if((n+m)&1) puts("0"),exit(0); m=n+m>>1;
jc[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) jc[i]=(ll)jc[i-1]*i%mod;
for(int i=2;i<=n;i++) inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i++) inv[i]=(ll)inv[i-1]*inv[i]%mod;
for(int i=1;i<=n;i++) qr(a[i]);
for(int i=1;i<=n;i++) qr(b[i]);
sort(a+1,a+n+1);
sort(b+1,b+n+1);
f[0]=1;
for(int i=1,k=0;i<=n;i++) {
while(k<n&&b[k+1]<a[i]) k++;
for(int j=k;j;j--)
f[j]=(f[j]+(ll)f[j-1]*max(0,k-(j-1)))%mod;
}
ll ans=0;
for(int i=m,F=1;i<=n;i++) {
f[i]=(ll)f[i]*jc[n-i]%mod;
ans+=(ll)F*jc[i]%mod*inv[i-m]%mod*f[i]%mod;
F=-F;
}
ans=(ans%mod+mod)%mod;
ans=ans*inv[m]%mod;
pr2(ans);
return 0;
}
4.BZOJ 2839 集合计数
一个有 个元素的集合有 个不同子集(包含空集),现在要在这 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 ,求取法的方案数,答案模1000000007。
第一次自己做出一道二项式反演的题,好激动啊
显然这是"至少"问题,我们直接确定并集的最小大小其他的暴力枚举即可.
设
为正好为
和至少为
的情况数.
如何理解第一个式子呢?
我们钦定
个元素的方案数显然为
,然后,剩余
个位置,形成了
个子集,这些子集要么选要么不选,即
种方案(注意,先算上部),同时要不为空集,所以-1即可.
暴力 并没有被卡.
int n,m;
ll jc[N],inv[N],ans;
ll power(ll a,ll b=mod-2,ll p=mod) {
ll c=1;
while(b) {
if(b&1) c=c*a%p;
b/=2; a=a*a%p;
}
return c;
}
ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}
int main() {
qr(n);qr(m);
jc[0]=1;for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
inv[n]=power(jc[n]);for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
for(int i=m,f=1;i<=n;i++)
ans+=f*C(i,m)*C(n,i)%mod*(power(2,power(2,n-i,mod-1))-1)%mod,f=-f;
pr2((ans%mod+mod)%mod);
return 0;
}
闲来无事,不妨卡卡常数.
我们显然可以预处理2的幂来加速.
对于指数部分
(费马小定理).
下面
.
然后,我们直接值域分块,可以用 完成预处理.
复杂度:
质的飞越:
int n,m;
ll jc[N],inv[N],ans,p1[M+5],p2[M+5],pw[N];
ll power(ll a,ll b=mod-2,ll p=mod) {
ll c=1;
while(b) {
if(b&1) c=c*a%p;
b/=2; a=a*a%p;
}
return c;
}
ll P1(int x) {return p1[x>>15]*p2[x&(M-1)]%mod;}
ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}
int main() {
qr(n);qr(m);
jc[0]=1;for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
inv[n]=power(jc[n]);for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
p2[0]=1;for(int i=1;i<=M;i++)p2[i]=p2[i-1]*2%mod;
p1[0]=1;p1[1]=p2[M]%mod;for(int i=2;i<M;i++) p1[i]=p1[i-1]*p1[1]%mod;
pw[0]=1;for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2%(mod-1);
for(int i=m,f=1;i<=n;i++)
ans+=f*C(i,m)*C(n,i)%mod*(P1(pw[n-i])-1)%mod,f=-f;
pr2((ans%mod+mod)%mod);
return 0;
}
我**了,可以发现 ,所以我们倒着推即可.
5.BZOJ 4710: [Jsoi2011]分特产
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=2010,mod=1e9+7;
int n,m,a[N];
ll ans,c[N][N],f;
void qr(int &x) {scanf("%d",&x);}
int main() {
qr(n); qr(m);
for(int i=0;i<=2000;i++) {
c[i][0]=1;
for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
for(int i=1;i<=m;i++) qr(a[i]);
for(int i=0,F=1,f;i<=n;i++) {
f=c[n][i];
for(int j=1;j<=m;j++)
f=f*c[n-i+a[j]-1][a[j]]%mod;
(ans+=F*f+mod)%=mod;
F=-F;
}
printf("%lld\n",ans);return 0;
}
6.CF285E Positions in Permutations
题意:给你一个长度为 的排列 和 ,求 的 的个数.
设 为恰好为 的方案数, 是至少为 的方案数.
则有:
比较难的地方是求 .
设计一个DP,定义 ,乱搞即可.
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2010,mod=1e9+7;
int n,m;
ll jc[N],inv[N],f[N][N][4],ans;
//f[i][j][k]表示前i个数中有j个好数,k的两位分别表示i,i+1位置是否取.
ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}
int main() {
scanf("%d %d",&n,&m);
jc[0]=inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod,inv[i]=inv[i]*inv[i-1]%mod;
f[1][1][1]=f[1][0][0]=1;
for(int i=2;i<=n;i++) {
f[i][0][0]=1;
for(int j=1;j<=i;j++)
f[i][j][0]=(f[i-1][j-1][0]+f[i-1][j][0]+f[i-1][j][2])%mod,
f[i][j][2]=(f[i-1][j-1][1]+f[i-1][j][1]+f[i-1][j][3])%mod,
f[i][j][1]=(f[i-1][j-1][0]+f[i-1][j-1][2])%mod,
f[i][j][3]=(f[i-1][j-1][1]+f[i-1][j-1][3])%mod;
}
ll g;
for(int i=m,F=1;i<=n;i++) {
g=F*(f[n][i][0]+f[n][i][2])*jc[n-i]%mod*C(i,m)%mod+mod;
(ans += g) %= mod; F=-F;
}
printf("%lld\n",ans); return 0;
}
7.Luogu P4491 [HAOI2018]染色
为了报答小 的苹果, 小 打算送给热爱美术的小 一块画布, 这块画布可 以抽象为一个长度为 的序列, 每个位置都可以被染成 种颜色中的某一种.
然而小 只关心序列的 个位置中出现次数恰好为 的颜色种数, 如果恰 好出现了 次的颜色有 种, 则小 C 会产生 的愉悦度.
小 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 取模的结果是多少.
.
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e7+10,M=1<<18|10,size=1<<20,mod=1004535809,g=3,G=334845270;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,lim,m,s,jc[N],inv[N],a[M],b[M],tr[M],ans,w[M];
ll power(ll a,ll b=mod-2) {
ll c=1;
while(b) {
if(b&1) c=c*a%mod;
b /= 2; a=a*a%mod;
}
return c;
}
void add(int &x,int y) {x+=y; if(x>=mod) x-=mod;}
void del(int &x,int y) {x-=y; if(x<0) x+=mod;}
void NTT(int *f,int e) {
for(int i=1;i<n;i++)
if(i<tr[i]) swap(f[i],f[tr[i]]);
for(int p=2,len=1;p<=n;len=p,p*=2) {
ll tag=power(e,(mod-1)/p);
for(int k=0;k<n;k+=p) {
ll buf=1,t;
for(int l=k;l<k+len;l++) {
t=f[l+len]*buf%mod;
f[l+len]=(f[l]-t+mod)%mod;
add(f[l],t);
buf=buf*tag%mod;
}
}
}
}
ll C(int x,int y) {return (ll)jc[x]*inv[y]%mod*inv[x-y]%mod;}
int main() {
qr(n); qr(m); qr(s);
for(int i=0;i<=m;i++) qr(w[i]);
lim=max(n,m);
jc[0]=1;for(int i=1;i<=lim;i++) jc[i]=(ll)jc[i-1]*i%mod;
inv[lim]=power(jc[lim]);for(int i=lim; i;i--) inv[i-1]=(ll)inv[i]*i%mod;
lim=min(m,n/s);
for(int i=0;i<=lim;i++) b[i]=(i&1?-inv[i]+mod:inv[i]);
ll t=1,T=inv[s];
for(int i=0;i<=lim;i++) a[lim-i]=jc[i]*C(m,i)%mod*jc[n]%mod*inv[n-i*s]%mod*t%mod*power(m-i,n-i*s)%mod,t=t*T%mod;
for(n=1;n<=lim;n<<=1);
n*=2;for(int i=1;i<n;i++) tr[i]=(tr[i>>1]>>1)|(i&1?n>>1:0);
NTT(a,g); NTT(b,g); for(int i=0;i<n;i++) a[i]=(ll)a[i]*b[i]%mod;
NTT(a,G); ll Inv=power(n); for(int i=0;i<=lim;i++) add(ans,a[lim-i]*Inv%mod*inv[i]%mod*w[i]%mod);
pr2(ans);
return 0;
}