文章目录
- 什么是反演?
- 正题:二项式反演
- 例题
- 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)
什么是反演?
类似这样 f n = ∑ i = 0 n A n , i ∗ g i f_n=\sum_{i=0}^n A_{n,i}* g_i fn=∑i=0nAn,i∗gi,现在已知 f f f,要你求 g g g.这就是一个反演,即一个反推的过程.
假设 g n = ∑ i = 0 n B n , i ∗ f i g_n=\sum_{i=0}^n B_{n,i}*f_i gn=∑i=0nBn,i∗fi.
这显然 A ∗ B = E A*B=E A∗B=E,得到单位矩阵,也就是满足 f n = ∑ i = 0 n A n , i ∗ ∑ j = 0 n B i , j ∗ f i = ∑ i = 0 n [ A ∗ B ] n , j ∗ f i = ∑ i = 0 n [ i = n ] ∗ f i f_n=\sum_{i=0}^n A_{n,i}*\sum_{j=0}^n B_{i,j}*f_i=\sum_{i=0}^n [A*B]_{n,j}*f_i=\sum_{i=0}^n [i=n]*f_i fn=∑i=0nAn,i∗∑j=0nBi,j∗fi=∑i=0n[A∗B]n,j∗fi=∑i=0n[i=n]∗fi.
即求和套反演为自身.
一些反演:莫比乌斯反演,二项式反演…(其实我就会这俩)
正题:二项式反演
二项式反演有三种形式: ( 0 ≤ n ≤ m ) (0\le n\le m) (0≤n≤m)
- f n = ∑ i = 0 n ( − 1 ) i ( n i ) g i ⇔ g n = ∑ i = 0 n ( − 1 ) i ( n i ) f i f_n=\sum_{i=0}^n (-1)^i \dbinom n i g_i\Leftrightarrow g_n=\sum_{i=0}^n (-1)^i \dbinom n if_i fn=∑i=0n(−1)i(in)gi⇔gn=∑i=0n(−1)i(in)fi(极强的对称性)
- f n = ∑ i = 0 n ( n i ) g i ⇒ g n = ∑ i = 0 n ( − 1 ) n − i ( n i ) f i f_n=\sum_{i=0}^n \dbinom n i g_i\Rightarrow g_n=\sum_{i=0}^n (-1)^{n-i} \dbinom n i f_i fn=∑i=0n(in)gi⇒gn=∑i=0n(−1)n−i(in)fi
- f n = ∑ i = n m ( i n ) g i ⇒ g n = ∑ i = n m ( − 1 ) n − i ( i n ) f i f_n=\sum_{i=n}^m \dbinom i n g_i\Rightarrow g_n=\sum_{i=n}^m (-1)^{n-i} \dbinom i n f_i fn=∑i=nm(ni)gi⇒gn=∑i=nm(−1)n−i(ni)fi
下面证明第一条,其他的证明类似,读者自证不难.
f
n
=
∑
i
=
0
n
(
−
1
)
i
(
n
i
)
g
i
=
∑
i
=
0
n
(
−
1
)
i
(
n
i
)
∑
j
=
0
i
(
−
1
)
j
(
i
j
)
f
j
f_n=\sum_{i=0}^n (-1)^i \dbinom n i g_i=\sum_{i=0}^n (-1)^i \dbinom n i \sum_{j=0}^i (-1)^j\dbinom i j f_j
fn=i=0∑n(−1)i(in)gi=i=0∑n(−1)i(in)j=0∑i(−1)j(ji)fj
=
∑
i
=
0
n
(
−
1
)
i
∑
j
=
0
i
(
−
1
)
j
(
n
i
)
(
i
j
)
f
j
=\sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n i \dbinom i j f_j
=i=0∑n(−1)ij=0∑i(−1)j(in)(ji)fj
由于:
(
n
i
)
(
i
j
)
=
n
!
i
!
(
n
−
i
)
!
∗
i
!
j
!
∗
(
i
−
j
)
!
=
n
!
(
n
−
i
)
!
∗
1
j
!
∗
(
i
−
j
)
!
=
n
!
(
n
−
j
)
!
j
!
∗
(
n
−
j
)
!
(
n
−
i
)
!
∗
(
i
−
j
)
!
=
(
n
j
)
(
n
−
j
n
−
i
)
\dbinom n i \dbinom i j=\dfrac{n!}{i!(n-i)!}*\dfrac{i!}{j!*(i-j)!}=\dfrac{n!}{(n-i)!}*\dfrac{1}{j!*(i-j)!}=\dfrac{n!}{(n-j)!j!}*\dfrac{(n-j)!}{(n-i)!*(i-j)!}=\dbinom n j \dbinom {n-j}{n-i}
(in)(ji)=i!(n−i)!n!∗j!∗(i−j)!i!=(n−i)!n!∗j!∗(i−j)!1=(n−j)!j!n!∗(n−i)!∗(i−j)!(n−j)!=(jn)(n−in−j)
0 0 = 1 ( 组 合 中 的 规 定 ) 0^0=1(组合中的规定) 00=1(组合中的规定)
所以:
∑
i
=
0
n
(
−
1
)
i
∑
j
=
0
i
(
−
1
)
j
(
n
i
)
(
i
j
)
f
j
\sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n i \dbinom i j f_j
i=0∑n(−1)ij=0∑i(−1)j(in)(ji)fj
=
∑
i
=
0
n
(
−
1
)
i
∑
j
=
0
i
(
−
1
)
j
(
n
j
)
(
n
−
j
n
−
i
)
f
j
=\sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n j \dbinom {n-j}{n-i}f_j
=i=0∑n(−1)ij=0∑i(−1)j(jn)(n−in−j)fj
=
∑
j
=
0
n
(
−
1
)
j
f
j
∗
(
n
j
)
∑
i
=
j
n
(
−
1
)
i
∗
(
n
−
j
n
−
i
)
=\sum_{j=0}^n (-1)^j f_j*\dbinom n j\sum_{i=j}^n(-1)^i*\dbinom{n-j}{n-i}
=j=0∑n(−1)jfj∗(jn)i=j∑n(−1)i∗(n−in−j)
=
∑
j
=
0
n
(
−
1
)
j
f
j
∗
(
n
j
)
∗
∑
i
=
0
n
−
j
(
−
1
)
n
−
i
∗
(
n
−
j
i
)
=\sum_{j=0}^n (-1)^j f_j *\dbinom n j*\sum_{i=0}^{n-j}(-1)^{n-i}*\dbinom{n-j}{i}
=j=0∑n(−1)jfj∗(jn)∗i=0∑n−j(−1)n−i∗(in−j)
=
∑
j
=
0
n
(
−
1
)
j
+
n
f
j
∗
(
n
j
)
∗
∑
i
=
0
n
−
j
(
−
1
)
i
∗
(
n
−
j
i
)
=\sum_{j=0}^n (-1)^{j+n} f_j *\dbinom n j*\sum_{i=0}^{n-j}(-1)^{i}*\dbinom{n-j}{i}
=j=0∑n(−1)j+nfj∗(jn)∗i=0∑n−j(−1)i∗(in−j)
=
∑
j
=
0
n
(
−
1
)
j
+
n
f
j
∗
(
n
j
)
∗
0
n
−
j
(
二
项
式
定
理
)
=\sum_{j=0}^n (-1)^{j+n} f_j *\dbinom n j*0^{n-j}(二项式定理)
=j=0∑n(−1)j+nfj∗(jn)∗0n−j(二项式定理)
=
∑
j
=
0
n
[
j
=
n
]
f
j
=\sum_{j=0}^n [j=n]f_j
=j=0∑n[j=n]fj
证毕!
应用方法:
当有
−
1
-1
−1的话用第一条.
用第二三条的时候,即"至多"/"至少"量比"正好"量更好计算时.
应用题可以戳最上面的链接看.
例题
1.BZOJ 4487: [Jsoi2015]染色问题
三维容斥-----对二项式定理的推广.
设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k表示
i
行
j
列
正
好
k
种
颜
色
i行j列正好k种颜色
i行j列正好k种颜色的方案数.
定义
g
i
,
j
,
k
g_{i,j,k}
gi,j,k表示
至
多
染
i
行
,
j
列
,
k
种
颜
色
至多染i行,j列,k种颜色
至多染i行,j列,k种颜色的方案数.
则有:
{
g
n
,
m
,
c
=
(
c
+
1
)
n
m
g
n
,
m
,
c
=
∑
i
=
0
n
∑
j
=
0
m
∑
k
=
0
c
f
i
,
j
,
k
∗
(
n
i
)
(
m
j
)
(
c
k
)
\begin{cases}g_{n,m,c}=(c+1)^{nm}\\g_{n,m,c}=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c f_{i,j,k}*\dbinom n i \dbinom m j \dbinom c k\end{cases}
⎩⎨⎧gn,m,c=(c+1)nmgn,m,c=∑i=0n∑j=0m∑k=0cfi,j,k∗(in)(jm)(kc)
反演:
f
n
,
m
,
c
=
∑
i
=
0
n
∑
j
=
0
m
∑
k
=
0
c
(
−
1
)
n
−
i
+
m
−
j
+
c
−
k
g
i
,
j
,
k
∗
(
n
i
)
(
m
j
)
(
c
k
)
=
∑
i
=
0
n
∑
j
=
0
m
∑
k
=
0
c
(
−
1
)
n
−
i
+
m
−
j
+
c
−
k
(
k
+
1
)
i
j
∗
(
n
i
)
(
m
j
)
(
c
k
)
f_{n,m,c}=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c (-1)^{n-i+m-j+c-k}g_{i,j,k}*\dbinom n i \dbinom m j \dbinom c k=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c (-1)^{n-i+m-j+c-k}(k+1)^{ij}*\dbinom n i \dbinom m j \dbinom c k
fn,m,c=∑i=0n∑j=0m∑k=0c(−1)n−i+m−j+c−kgi,j,k∗(in)(jm)(kc)=∑i=0n∑j=0m∑k=0c(−1)n−i+m−j+c−k(k+1)ij∗(in)(jm)(kc)
可理解为对每一维依次反演.
显然暴力需要:
O
(
n
3
log
n
)
O(n^3 \log n)
O(n3logn)的复杂度.这样会T^T.
如果预处理幂的话也是
O
(
n
3
)
O(n^3)
O(n3)的,依然过不了.
观察式子可以发现有幂次和组合数,我们尝试用二项式定理进行合并.
即
(
−
1
)
m
−
j
∗
[
(
k
+
1
)
i
]
j
∗
C
m
j
=
[
(
k
+
1
)
i
−
1
]
j
(-1)^{m-j}*[(k+1)^i]^j*C_m^j=[(k+1)^i-1]^j
(−1)m−j∗[(k+1)i]j∗Cmj=[(k+1)i−1]j
预处理幂->
O
(
n
2
)
O(n^2)
O(n2).
计算复杂度->
O
(
n
2
log
n
)
O(n^2 \log n)
O(n2logn).
#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
题意:给你个 n ∗ n n*n n∗n的矩阵,每个格子可填 [ 1 , k ] [1,k] [1,k],求每行每列的最小值为1的方案数.
可以发现 [ 2 , k ] [2,k] [2,k]的每个数是无区别的.
定义状态 f [ i ] [ j ] 表 示 至 少 i 行 j 列 不 合 法 的 情 况 数 , f [ i ] [ j ] = ( k − 1 ) n ( i + j ) − i ∗ j k n 2 − ( i + j ) n + i ∗ j f[i][j]表示至少i行j列不合法的情况数,f[i][j]=(k-1)^{n(i+j)-i*j}k^{n^2-(i+j)n+i*j} f[i][j]表示至少i行j列不合法的情况数,f[i][j]=(k−1)n(i+j)−i∗jkn2−(i+j)n+i∗j, g [ i ] [ j ] 表 示 恰 好 i 行 j 列 不 合 法 的 情 况 数 g[i][j]表示恰好i行j列不合法的情况数 g[i][j]表示恰好i行j列不合法的情况数
f [ a ] [ b ] = ∑ i = a n ∑ j = 1 n C n i C n j g [ i ] [ j ] ⇔ g [ a ] [ b ] = ∑ i = a n ∑ j = b ( − 1 ) 2 n − i − j C i a C j b f [ i ] [ j ] f[a][b]=\sum_{i=a}^n \sum_{j=1}^n C_n^i C_n^j g[i][j]\Leftrightarrow g[a][b]=\sum_{i=a}^n\sum_{j=b}(-1)^{2n-i-j}C_i^a C_j^b f[i][j] f[a][b]=∑i=an∑j=1nCniCnjg[i][j]⇔g[a][b]=∑i=an∑j=b(−1)2n−i−jCiaCjbf[i][j]
g [ 0 ] [ 0 ] g[0][0] g[0][0]即为答案.
#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 已经没有什么好害怕的了
因为输入的两个数组( a , b a,b a,b)的数互不相等,所以可得 ( a > b ) 的 个 数 = ( n + k ) / 2 (a>b)的个数=(n+k)/2 (a>b)的个数=(n+k)/2(以下令 k = ( n + k ) / 2 k=(n+k)/2 k=(n+k)/2).
设 f [ i ] , g [ i ] f[i],g[i] f[i],g[i]分别表示正好和至少为 i 对 ( a > b ) i对(a>b) i对(a>b)的方案数.
我们设法构造 g − > O ( n 2 ) D P g->O(n^2)DP g−>O(n2)DP
把两个数组都排序,定义
d
p
[
i
]
[
j
]
,
p
o
s
[
i
]
分
别
表
示
前
i
个
中
选
择
j
对
,
比
a
[
i
]
小
的
b
的
数
量
dp[i][j],pos[i]分别表示前i个中选择j对,比a[i]小的b的数量
dp[i][j],pos[i]分别表示前i个中选择j对,比a[i]小的b的数量.
所以:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
f
[
i
−
1
]
[
j
−
1
]
∗
m
a
x
(
0
,
p
o
s
[
i
]
−
(
j
−
1
)
)
(
不
一
定
能
凑
对
成
功
)
f[i][j]=f[i-1][j]+f[i-1][j-1]*max(0,pos[i]-(j-1))(不一定能凑对成功)
f[i][j]=f[i−1][j]+f[i−1][j−1]∗max(0,pos[i]−(j−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=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 集合计数
一个有 N N N个元素的集合有 2 N 2^N 2N个不同子集(包含空集),现在要在这 2 N 2^N 2N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 K K K,求取法的方案数,答案模1000000007。
第一次自己做出一道二项式反演的题,好激动啊
显然这是"至少"问题,我们直接确定并集的最小大小其他的暴力枚举即可.
设
f
[
i
]
,
g
[
i
]
f[i],g[i]
f[i],g[i]为正好为
i
i
i和至少为
i
i
i的情况数.
g
[
a
]
=
∑
i
=
a
n
(
i
a
)
f
[
i
]
=
(
n
a
)
(
2
2
n
−
a
−
1
)
g[a]=\sum_{i=a}^n \dbinom i a f[i]=\dbinom n a (2^{2^{n-a}}-1)
g[a]=∑i=an(ai)f[i]=(an)(22n−a−1)
f
[
k
]
=
∑
i
=
k
n
(
−
1
)
i
−
k
(
i
k
)
g
[
i
]
=
∑
i
=
k
n
(
−
1
)
i
−
k
(
i
k
)
(
n
i
)
(
2
2
n
−
i
−
1
)
f[k]=\sum_{i=k}^n (-1)^{i-k} \dbinom i k g[i]=\sum_{i=k}^n (-1)^{i-k} \dbinom i k \dbinom n i (2^{2^{n-i}}-1)
f[k]=∑i=kn(−1)i−k(ki)g[i]=∑i=kn(−1)i−k(ki)(in)(22n−i−1)
如何理解第一个式子呢?
我们钦定
a
a
a个元素的方案数显然为
(
n
a
)
\dbinom n a
(an),然后,剩余
n
−
a
n-a
n−a个位置,形成了
2
n
−
a
2^{n-a}
2n−a个子集,这些子集要么选要么不选,即
2
2
n
−
a
2^{2^{n-a}}
22n−a种方案(注意,先算上部),同时要不为空集,所以-1即可.
暴力 O ( n log 2 m o d ) O(n\log^2\mod) O(nlog2mod)并没有被卡.
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的幂来加速.
对于指数部分
%
(
m
o
d
−
1
)
\% (mod-1)
%(mod−1)(费马小定理).
下面
%
m
o
d
\% mod
%mod.
然后,我们直接值域分块,可以用 O ( m o d ) O(\sqrt mod) O(mod)完成预处理.
复杂度: O ( n + m o d ) O(n+\sqrt mod) O(n+mod)
质的飞越:
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;
}
我**了,可以发现 2 2 i = ( 2 2 i − 1 ) 2 2^{2^i}=(2^{2^{i-1}})^2 22i=(22i−1)2,所以我们倒着推即可.
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
题意:给你一个长度为 n n n的排列 p p p和 m m m,求 f ( p ) = ∑ i = 1 n [ ∣ p i − i ∣ = 1 ] = m f(p)=\sum_{i=1}^n[|p_i-i|=1]=m f(p)=∑i=1n[∣pi−i∣=1]=m的 p p p的个数.
设 f ( i ) f(i) f(i)为恰好为 i i i的方案数, g ( i ) g(i) g(i)是至少为 i i i的方案数.
则有:
{
g
(
a
)
=
∑
i
=
a
n
(
i
m
)
f
(
i
)
f
(
a
)
=
∑
i
=
a
n
(
−
1
)
i
−
a
(
i
a
)
g
(
i
)
\begin{cases}g(a)=\sum_{i=a}^n \dbinom i m f(i)\\f(a)=\sum_{i=a}^n (-1)^{i-a}\dbinom i a g(i)\end{cases}
⎩⎪⎪⎨⎪⎪⎧g(a)=∑i=an(mi)f(i)f(a)=∑i=an(−1)i−a(ai)g(i)
比较难的地方是求 g g g.
设计一个DP,定义 d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] 表 示 前 i 个 数 中 有 j 个 满 足 条 件 的 数 , 三 四 维 表 示 i , i + 1 是 否 被 选 择 dp[i][j][0/1][0/1]表示前i个数中有j个满足条件的数,三四维表示i,i+1是否被选择 dp[i][j][0/1][0/1]表示前i个数中有j个满足条件的数,三四维表示i,i+1是否被选择,乱搞即可.
#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 C C 的苹果, 小 G G G 打算送给热爱美术的小 C C C 一块画布, 这块画布可 以抽象为一个长度为 N N N 的序列, 每个位置都可以被染成 M M M 种颜色中的某一种.
然而小 C C C 只关心序列的 N N N 个位置中出现次数恰好为 S S S 的颜色种数, 如果恰 好出现了 S S S 次的颜色有 K K K 种, 则小 C 会产生 W k W_k Wk的愉悦度.
小 C C C 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 1004535809 1004535809 1004535809 取模的结果是多少.
N ≤ 1 0 7 , M ≤ 1 0 5 N\le 10^7,M\le 10^5 N≤107,M≤105.
#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;
}