NOIP数学学习笔记 Sakura_xyz

1.组合数学的基本定义与基础题型

很水,可以选择性阅读。

例题:洛谷 P5135 painting
给定一个网格,每一列一个元素涂成黑色,求单调下降与单调不升的方案数,对 1 0 9 + 7 10^9+7 109+7 取模。
单调下降 :显然 C n m C _n^m Cnm
单调不升 :显然 C n + m – 1 m C_{n + m – 1}^m Cn+m–1m
考虑位置 a i + i a_i + i ai+i 是单调的,且在 [ 2 , n + m ] [ 2 , n + m ] [2,n+m] 区间内,选 m 种,共有 C n + m – 1 m C_{n + m – 1}^m Cn+m–1m 种选择方式。

#include
#include
#define MAXN 1000005

using namespace std;

const int mod=1e9+7;

int inv[MAXN];

int C(long long n,long long m){
	if(n<m) return 0;
	int ans=1;
	for(long long i=1;i<=m;i++)
		ans=1ll*(n-i+1)%mod*inv[i]%mod*ans%mod;
	return ans;
}

void solve(){
	long long n,m,opt;
	cin >> n >> m >> opt;
	if(opt==1){
		cout << C(n,m) << endl;
	}
	else{
		cout << C(n+m-1,m) << endl;
	}
}

int main(){
	inv[1]=1;
	for(int i=2;i<MAXN;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

例题:洛谷 P8432 「WHOI-2」ぽかぽかの星

一场有两道大模拟的公开赛的签到题。

求满足以下条件的长度为 n n n 的数列的数量,对 1 0 9 + 7 10^9+7 109+7 取模:

  • 0 < a 1 ≤ a 2 ≤ a 3 ⋯ ≤ a n ≤ k 00<a1a2a3ank
  • ∀ i ≠ j , a i + a j ≠ k + 1 \forall i\not = j,a_i+a_j\not = k+1 i=j,ai+aj=k+1

k k k 奇偶性讨论,将两个不能同时出现的数放在一组。

k k k 为偶数时,有 k 2 \frac{k}{2} 2k 组,枚举选的组数,再计算对于所有组数的答案。
即:
∑ i = 1 k 2 C k 2 i C n − 1 i − 1 2 i \sum_{i = 1}^{\frac{k}{2}} C_{\frac{k}{2}}^iC_{n - 1}^{i - 1}2^i i=12kC2kiCn1i12i
k k k 为奇数时,需要再计算 k + 1 2 \frac{k+1}{2} 2k+1 是否出现过的答案。
即:
∑ i = 1 k 2 C k 2 i C n − 1 i − 1 2 i + ∑ i = 1 k 2 C k 2 i C n − 2 i − 1 2 i \sum_{i = 1}^{\frac{k}{2}} {C_{\frac{k}{2}}^iC_{n - 1}^{i - 1}2^i} +\sum_{i = 1}^{\frac{k}{2}} C_{\frac{k}{2}}^iC_{n - 2}^{i - 1}2^i i=12kC2kiCn1i12i+i=12kC2kiCn2i12i

#include
#include
#define MAXN 5000005

using namespace std;

const int mod=1e9+7;

int Fp(int x,int tms){
	int ret=1;
    while(tms){
    	if(tms&1) ret=1ll*ret*x%mod;
    	x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int fac[MAXN],invfac[MAXN],base2[MAXN];

int C(int n,int m){
	if(n<m) return 0;
	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}

void solve(){
	int n,k,ans=0;
	cin >> n >> k;
	if(n==1){
		cout << k << endl;
		return;
	}
	if(k%2==1){
		for(int i=1;i<=k/2;i++){
			ans=(ans+1ll*C(k/2,i)*C(n-1,i-1)%mod*base2[i]%mod)%mod;
			ans=(ans+1ll*C(k/2,i)*C(n-2,i-1)%mod*base2[i]%mod)%mod;
		}
		cout << ans << endl;
	}
	else{
		for(int i=1;i<=k/2;i++){
			ans=(ans+1ll*C(k/2,i)*C(n-1,i-1)%mod*base2[i]%mod)%mod;
		}
		cout << ans << endl;
	}
}

int main(){
	fac[0]=base2[0]=1;
	for(int i=1;i<MAXN;i++) fac[i]=1ll*fac[i-1]*i%mod,base2[i]=(2ll*base2[i-1])%mod;
	invfac[MAXN-1]=Fp(fac[MAXN-1],mod-2);
	for(int i=MAXN-2;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

例题:CF294C Shaass and Lights

n n n 盏灯,其中亮了 m m m 盏,现每次可点亮一盏与亮的灯相邻的灯,求将所有的灯都点亮的方案数。

考虑初始时亮的灯把序列分成了 m + 1 m + 1 m+1 段,我们在每次操作时,选择其中的一段点亮一盏灯。根据可重集排列的性质,有:
a n s = ( n − m ) ! ∏ i = 1 m + 1 l e n i ! ans = \frac{(n - m)!}{\prod_{i = 1}^{m + 1} len_i!} ans=i=1m+1leni!(nm)!

其中 l e n i len_i leni 为分离出的第 i i i 个区间的长度。

然后再考虑对于除最左侧的段和最右侧的段以外,其余的段在每次的选择时都有两种方案,选择最左侧的一盏灯点亮,或者选择最右侧的一盏灯点亮,因此,共有 2 n − m − l e n 1 − l e n m + 1 2^{n - m - len_1 - len_{m + 1}} 2nmlen1lenm+1 种选择方式。

相乘即得最终答案。

#include
#include
#include
#include
#define MAXN 100005

using namespace std;

const int mod=1e9+7;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int n,m,base2[MAXN],fac[MAXN],invfac[MAXN],a[MAXN];

bool flag[MAXN];

int main(){
	ios :: sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m;
	base2[0]=fac[0]=1;
	for(int i=1;i<=m;i++) cin >> a[i];
	stable_sort(a+1,a+m+1);
	for(int i=1;i<=n;i++){
		fac[i]=1ll*fac[i-1]*i%mod;
		base2[i]=(base2[i-1]+base2[i-1])%mod;
	}
	invfac[n]=Fp(fac[n],mod-2);
	for(int i=n-1;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	int ans=fac[n-m];
	for(int i=1;i<=m;i++){
		int tmp=a[i]-a[i-1]-1;
		if(tmp==0) continue;
		ans=1ll*ans*invfac[tmp]%mod;
		if(i!=1) ans=1ll*ans*base2[tmp-1]%mod;
	}
	if(a[m]!=n) ans=1ll*ans*invfac[n-a[m]]%mod;
	cout << ans << endl;
	return 0;
}

2.容斥原理在组合数学中的思想及应用

例题:洛谷 P5339 [TJOI2019]唱、跳、rap和篮球
A A A 个人喜欢唱, B B B 个人喜欢跳, C C C 个人喜欢 rap , D D D 个人喜欢篮球,选 N N N 个人排成一列,如果连续的 4 4 4 个人分别喜欢唱,跳, rap ,篮球,则他们会讨论蔡徐坤,现需要求出没有人讨论蔡徐坤的排列方案数,有同样爱好的人被视为同样的。

N ≤ 1000 , A , B , C , D ≤ 500 N\leq1000,A,B,C,D\leq500 N1000,A,B,C,D500

定义 f ( n , a , b , c , d ) f(n,a,b,c,d) f(n,a,b,c,d) a a a 个喜欢唱, b b b 个喜欢跳, c c c 个喜欢 rap , d d d 个喜欢篮球的人任意排列成 n n n 个人的方案数,对 998244353 998244353 998244353 取模。

这个东西显然等于

∑ i = 1 a ∑ j = 1 b ∑ k = 1 c ∑ l = 1 d [ i + j + k + l = n ] × n ! i ! j ! k ! l ! \sum_{i = 1}^{a}\sum_{j = 1}^{b}\sum_{k = 1}^{c}\sum_{l = 1}^{d}[i + j + k + l = n] \times \frac{n!}{i!j!k!l!} i=1aj=1bk=1cl=1d[i+j+k+l=n]×i!j!k!l!n!

n ! n! n! 提出来,里面很显然能 NTT , O ( n log ⁡ n ) O(n\log n) O(nlogn) 能求。

之后考虑容斥,有:

a n s = ∑ i = 0 ⌊ n 4 ⌋ ( − 1 ) i C n − 3 i i × f ( N − 4 i , A − i , B − i , C − i , D − i ) ans = \sum_{i = 0}^{\lfloor\frac{n}{4}\rfloor}(-1)^i C_{n - 3i}^{i} \times f(N - 4i , A - i , B - i , C - i , D - i) ans=i=04n(1)iCn3ii×f(N4i,Ai,Bi,Ci,Di)

考虑枚举至少有 i i i 组人讨论蔡徐坤,剩下的人任意排列的方案。我们把连续 4 4 4 个讨论蔡徐坤的人看成一个蔡徐坤,总共 n − 3 i n - 3i n3i 个人里面挑出 i i i 个人当蔡徐坤即可。

#include
#include
#define MAXN 2005

using namespace std;

const int mod=998244353;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int n,A,B,C,D,a[MAXN<<2],b[MAXN<<2],c[MAXN<<2],d[MAXN<<2],rev[MAXN<<2];

int fac[MAXN],invfac[MAXN];

void NTT(int * a,int lim,int flag){
	for(int i=0;i<lim;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int mid=1;mid<lim;mid<<=1){
		int tmp=Fp(flag==1?3:Fp(3,mod-2),(mod-1)/(mid<<1));
		for(int i=0;i<lim;i+=(mid<<1)){
			int x=1;
			for(int j=0;j<mid;j++,x=1ll*x*tmp%mod){
				int t1=a[i+j],t2=a[i+mid+j];
				a[i+j]=(t1+1ll*x*t2%mod)%mod;
				a[i+mid+j]=(t1-1ll*x*t2%mod+mod)%mod;
			}
		}
	}
	if(flag==-1){
		int inv=Fp(lim,mod-2);
		for(int i=0;i<lim;i++) a[i]=1ll*a[i]*inv%mod;
	}
}

int C_(int n,int m){
	if(n<m) return 0;
	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}

int main(){
	cin >> n >> A >> B >> C >> D;
	fac[0]=1;
	for(int i=1;i<MAXN;i++) fac[i]=1ll*fac[i-1]*i%mod;
	invfac[MAXN-1]=Fp(fac[MAXN-1],mod-2);
	for(int i=MAXN-2;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	int ans=0;
	for(int i=0;i<=n/4&&i<=A&&i<=B&&i<=C&&i<=D;i++){
		int lim=1,l=0;
		while(lim<=A+B+C+D-4*i) lim<<=1,l++;
		for(int j=0;j<lim;j++){
			rev[j]=(rev[j>>1]>>1)|((j&1)<<l-1);
			if(j<=A-i) a[j]=invfac[j];
			else a[j]=0;
			if(j<=B-i) b[j]=invfac[j];
			else b[j]=0;
			if(j<=C-i) c[j]=invfac[j];
			else c[j]=0;
			if(j<=D-i) d[j]=invfac[j];
			else d[j]=0;
		}
		NTT(a,lim,1); NTT(b,lim,1); NTT(c,lim,1); NTT(d,lim,1);
		for(int i=0;i<lim;i++) a[i]=1ll*a[i]*b[i]%mod*c[i]%mod*d[i]%mod;
		NTT(a,lim,-1);
		int tmpans=1ll*a[n-4*i]*fac[n-4*i]%mod*C_(n-3*i,i)%mod;
		if(i&1) ans=(ans-tmpans+mod)%mod;
		else ans=(ans+tmpans)%mod;
	}
	cout << ans << endl;
	return 0;
}

例题:洛谷 P6295 有标号DAG计数
分别求由 1 − n 1 - n 1n 个点弱联通 DAG 的个数,对 998244353 998244353 998244353 取模, n ≤ 1 0 5 n\leq10^5 n105
(本来不打算讲的,但既然发的资料里有就讲吧)
首先,考虑不要求弱联通的情况
考虑容斥,设函数 f ( x ) f(x) f(x) 为由 x x x 个点组成的 DAG 的数量,有:

f ( n ) = ∑ i = 1 n ( − 1 ) i − 1 × C n i × f ( n − i ) × 2 i × ( n − i ) f(n) = \sum_{i = 1}^n (-1)^{i - 1}\times C_n^i \times f(n - i) \times 2^{i \times (n - i)} f(n)=i=1n(1)i1×Cni×f(ni)×2i×(ni)

考虑组合意义其实是显然的,枚举至少有多少个出度为 0 0 0 的节点,再容斥一下就做完了,但是因为我们的重点在容斥原理,所以再详细说明一下。

我们考虑,选出 i i i 个出度为 0 0 0 的节点时,剩下的节点仍然构成一个规模为 n − i n - i ni 的 DAG ,之后再考虑这两部分的关系,我们既然已经钦定了 n − i n - i ni 个点所形成的 DAG 的形态,那么,连接在这些点之间的边就是确定的,而可能造成不同情况贡献的答案必然在这两部分之间,那么,也就意味着,目前我们拥有选择空间的边总共有 i × ( n − i ) i \times (n - i) i×(ni) 条,这其中的每一条都有连边与不连边两种方式可供选择。此时,我们保证了构造出的 DAG 至少有 i i i 个出度为 0 0 0 的节点,将 i i i 1 1 1 枚举到 n n n ,再容斥一下,就能得出上式。

O ( n log ⁡ n ) O(n\log n) O(nlogn) 暴力转移显然,不再赘述,我们考虑优化。

考虑 C n i C_n^i Cni 摆出来了,不妨凑个 EGF 形式。

f ( n ) = ∑ i = 1 n ( − 1 ) i − 1 × C n i × f ( n − i ) × 2 i × ( n − i ) f(n) = \sum_{i = 1}^n (-1)^{i - 1}\times C_n^i \times f(n - i) \times 2^{i \times (n - i)} f(n)=i=1n(1)i1×Cni×f(ni)×2i×(ni)
= ∑ i = 1 n ( − 1 ) i − 1 × C n i × f ( n − i ) × 2 C n 2 − C i 2 − C n − i 2 = \sum_{i = 1}^n (-1)^{i - 1}\times C_n^i \times f(n - i) \times 2^{C_n^2 - C_i^2 - C_{n - i}^2} =i=1n(1)i1×Cni×f(ni)×2Cn2Ci2Cni2
= ∑ i = 1 n ( − 1 ) i − 1 × C n i × f ( n − i ) × 2 C n 2 2 C i 2 × 2 C n − i 2 = \sum_{i = 1}^n (-1)^{i - 1}\times C_n^i \times f(n - i) \times \frac{2^{C_n^2}}{2^{C_i^2}\times 2^{C_{n - i}^2}} =i=1n(1)i1×Cni×f(ni)×2Ci2×2Cni22Cn2
= ∑ i = 1 n ( − 1 ) i − 1 × n ! i ! × ( n − i ) ! × f ( n − i ) × 2 C n 2 2 C i 2 × 2 C n − i 2 = \sum_{i = 1}^n (-1)^{i - 1}\times \frac{n!}{i!\times (n - i)!} \times f(n - i) \times \frac{2^{C_n^2}}{2^{C_i^2}\times 2^{C_{n - i}^2}} =i=1n(1)i1×i!×(ni)!n!×f(ni)×2Ci2×2Cni22Cn2
= 2 C n 2 × n ! × ∑ i = 1 n ( − 1 ) i − 1 i ! × 2 C i 2 × f ( n − i ) ( n − i ) ! × 2 C n − i 2 = 2^{C_n^2} \times n! \times \sum_{i = 1}^n \frac{(-1)^{i - 1}}{i!\times 2^{C_i^2}}\times \frac{f(n - i)}{(n - i)! \times 2^{C_{n - i}^2}} =2Cn2×n!×i=1ni!×2Ci2(1)i1×(ni)!×2Cni2f(ni)

这样卷积形式就被我们搞出来了,同时还能发现一些优秀的性质,即:
f ( n ) 2 C n 2 × n ! = ∑ i = 1 n ( − 1 ) i − 1 i ! × 2 C i 2 × f ( n − i ) ( n − i ) ! × 2 C n − i 2 \frac{f(n)}{2^{C_n^2}\times n!} = \sum_{i = 1}^n \frac{(-1)^{i - 1}}{i!\times 2^{C_i^2}}\times \frac{f(n - i)}{(n - i)! \times 2^{C_{n - i}^2}} 2Cn2×n!f(n)=i=1ni!×2Ci2(1)i1×(ni)!×2Cni2f(ni)
左侧的式子和卷积的右半边的式子是一样的。

定义生成函数:
F ( x ) = ∑ i f ( i ) 2 C i 2 × i ! F(x) = \sum_i \frac{f(i)}{2^{C_i^2}\times i!} F(x)=i2Ci2×i!f(i)
G ( x ) = ∑ i ( − 1 ) i − 1 i ! × 2 C i 2 G(x) = \sum_i \frac{(-1)^{i - 1}}{i! \times 2^{C_i^2}} G(x)=ii!×2Ci2(1)i1
F ( x ) = F ( x ) × G ( x ) + f ( 0 ) = F ( x ) × G ( x ) + 1 F(x) = F(x) \times G(x) + f(0) = F(x) \times G(x) + 1 F(x)=F(x)×G(x)+f(0)=F(x)×G(x)+1

根据初中因式分解知识:
( 1 − G ( x ) ) × F ( x ) = 1 , F ( x ) = 1 1 − G ( x ) (1 - G(x))\times F(x) = 1,F(x) = \frac{1}{1 - G(x)} (1G(x))×F(x)=1,F(x)=1G(x)1

F ( x ) F(x) F(x) 可以求,也就意味着现在 f ( x ) f(x) f(x) 也可以求出来。

然后我们再考虑弱连通图与一般图间的关系。

这里有一个结论:定义 A ( x ) A(x) A(x) 为一般图的 EGF , B ( x ) B(x) B(x) 为弱连通图的 EGF ,则 A ( x ) = exp ⁡ ( B ( x ) ) A(x) = \exp (B(x)) A(x)=exp(B(x)) ,设 f ( x ) f(x) f(x) 的 EGF 为 F ′ ( x ) F'(x) F(x) ln ⁡ ( F ′ ( x ) ) \ln (F'(x)) ln(F(x)) 就是我们想要的答案。

虽然大家都知道这个结论,但是我们需要证明一下:

首先,定义在一个弱连通图的大小为 i i i 的点集中的答案为 f ( i ) f(i) f(i) ,在一个由 i i i 个大小总和为 j j j 的弱连通点集所组成的点集中的答案为 F i ( j ) F_{i}(j) Fi(j)

则:
F n ( m ) = 1 n ∑ i = 0 m C m i × F n − 1 ( m − i ) × f ( i ) F_{n}(m) = \frac{1}{n} \sum_{i = 0}^mC_m^i \times F_{n - 1}(m - i) \times f(i) Fn(m)=n1i=0mCmi×Fn1(mi)×f(i)

我们设 G n ( x ) G_n(x) Gn(x) F n ( x ) F_n(x) Fn(x) 的 EGF , H ( x ) H(x) H(x) f ( x ) f(x) f(x) 的 EGF 。
很显然这是个用 EGF 卷积的形式,即:
G n = G n − 1 × 1 n × H = H n n ! G_n = G_{n - 1} \times \frac{1}{n} \times H = \frac{H^n}{n!} Gn=Gn1×n1×H=n!Hn
可以发现这样就变成了 exp ⁡ ( H ) \exp (H) exp(H) 的泰勒展开形式,证毕。

代码就是一次多项式求逆和多项式 ln ⁡ \ln ln ,直接把封装好的板子拿来用就行了。

#include
#include
#include
#define MAXN 100005

using namespace std;

const int mod=998244353;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int rev[MAXN<<2];

void NTT(int * a,int lim,int flag){
	for(int i=0;i<lim;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int mid=1;mid<lim;mid<<=1){
		int tmp=Fp(flag==1?3:Fp(3,mod-2),(mod-1)/(mid<<1));
		for(int i=0;i<lim;i+=(mid<<1)){
			int x=1;
			for(int j=0;j<mid;j++,x=1ll*x*tmp%mod){
				int t1=a[i+j],t2=a[i+mid+j];
				a[i+j]=(t1+1ll*x*t2%mod)%mod;
				a[i+mid+j]=(t1-1ll*x*t2%mod+mod)%mod;
			}
		}
	}
	if(flag==-1){
		int inv=Fp(lim,mod-2);
		for(int i=0;i<lim;i++) a[i]=1ll*a[i]*inv%mod;
	}
}

int tmp[MAXN<<2];

void getinv(int deg,int * a,int * b){
	if(deg==1) return (void) (b[0]=Fp(a[0],mod-2));
	getinv(deg+1>>1,a,b);
	int lim=1,l=0;
	while(lim<=deg+deg) lim<<=1,l++;
	for(int i=0;i<lim;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
	for(int i=0;i<deg;i++) tmp[i]=a[i];
	for(int i=deg;i<lim;i++) tmp[i]=0;
	NTT(tmp,lim,1); NTT(b,lim,1);
	for(int i=0;i<lim;i++)
		b[i]=(2ll*b[i]%mod+mod-1ll*tmp[i]*b[i]%mod*b[i]%mod)%mod;
	NTT(b,lim,-1);
	for(int i=deg;i<lim;i++) b[i]=0;
}

void Derivation(int * a,int * b,int len){
	for(int i=1;i<len;i++) b[i-1]=1ll*i*a[i]%mod;
	b[len-1]=0;
}

void Integration(int * a,int * b,int len){
	b[0]=0;
	for(int i=1;i<len;i++) b[i]=1ll*a[i-1]*Fp(i,mod-2)%mod;
}

int X[MAXN<<2],Y[MAXN<<2];

void get_Ln(int * a,int * b,int len){
    Derivation(a,X,len);
	getinv(len,a,Y);
	int lim=1,l=0;
	while(lim<=len+len) lim<<=1,l++;
	for(int i=0;i<lim;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
	NTT(X,lim,1); NTT(Y,lim,1);
	for(int i=0;i<lim;i++) X[i]=1ll*X[i]*Y[i]%mod;
	NTT(X,lim,-1);
	Integration(X,b,len);
}

int a[MAXN<<2],b[MAXN<<2],c[MAXN<<2],fac[MAXN],invfac[MAXN];

int main(){
	int n; cin >> n;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
	invfac[n]=Fp(fac[n],mod-2);
	for(int i=n-1;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	a[0]=1;
	for(int i=1;i<=n;i++){
		a[i]=1ll*invfac[i]*Fp(Fp(2,1ll*(1ll*i*(i-1)/2ll)%(mod-1)),mod-2)%mod;
		if(i&1) a[i]=mod-a[i];
	}
	getinv(n+1,a,b);
	for(int i=0;i<=n;i++) b[i]=1ll*Fp(2,1ll*(1ll*i*(i-1)/2ll)%(mod-1))*b[i]%mod;
	get_Ln(b,c,n+1);
	for(int i=1;i<=n;i++) cout << 1ll*c[i]*fac[i]%mod << '\n';
	return 0;
}

3.二项式反演与广义容斥基础

二项式反演:
形式 1 1 1
若:
f ( x ) = ∑ i = 0 x C x i × g ( i ) f(x) = \sum_{i = 0}^xC_x^i\times g(i) f(x)=i=0xCxi×g(i)
则:
g ( x ) = ∑ i = 0 x ( − 1 ) x − i × C n i × f ( i ) g(x) = \sum_{i = 0}^x(-1)^{x - i}\times C_n^i\times f(i) g(x)=i=0x(1)xi×Cni×f(i)

证明:
考虑把 g ( x ) g(x) g(x) 带进去
有:
f ( x ) = ∑ i = 0 x C x i × g ( i ) = ∑ i = 0 x C x i × ∑ j = 0 i ( − 1 ) i − j × C i j × f ( j ) f(x) = \sum_{i = 0}^xC_x^i\times g(i) = \sum_{i = 0}^xC_x^i\times \sum_{j = 0}^i(-1)^{i - j}\times C_i^j \times f(j) f(x)=i=0xCxi×g(i)=i=0xCxi×j=0i(1)ij×Cij×f(j)

f ( x ) = ∑ j = 0 x f ( j ) ∑ i = j x C x i × C i j × ( − 1 ) i − j f(x) = \sum_{j = 0}^xf(j)\sum_{i = j}^xC_x^i\times C_i^j\times (-1)^{i - j} f(x)=j=0xf(j)i=jxCxi×Cij×(1)ij

f ( x ) = ∑ j = 0 x f ( j ) ∑ i = j x C x j × C x − j i − j × ( − 1 ) i − j f(x) = \sum_{j = 0}^xf(j)\sum_{i = j}^xC_x^j\times C_{x - j}^{i - j}\times (-1)^{i - j} f(x)=j=0xf(j)i=jxCxj×Cxjij×(1)ij

f ( x ) = ∑ j = 0 x f ( j ) × C x j ∑ t = 0 x − j C x − j t × ( − 1 ) t f(x) = \sum_{j = 0}^xf(j)\times C_x^j \sum_{t = 0}^{x - j}C_{x - j}^{t}\times (-1)^t f(x)=j=0xf(j)×Cxjt=0xjCxjt×(1)t

x ≠ j x \not=j x=j 时,很显然,由二项式定理,后面那堆东西是等于 0 0 0 的,否则等于 1 1 1
证毕。

很显然可以发现枚举不一定要从 0 0 0 开始,从任意位置开始枚举都满足这个式子。

形式 2 2 2
若:
f ( x ) = ∑ i = x n C i x × g ( i ) f(x) = \sum_{i = x}^n C_i^x\times g(i) f(x)=i=xnCix×g(i)

则:
g ( x ) = ∑ i = x n ( − 1 ) i − x × C i x × f ( i ) g(x) = \sum_{i = x}^n (-1)^{i - x}\times C_i^x\times f(i) g(x)=i=xn(1)ix×Cix×f(i)

g ( x ) g(x) g(x) 带入:

f ( x ) = ∑ i = x n C i x × ∑ j = i n ( − 1 ) j − i × C j i × f ( j ) f(x) = \sum_{i = x}^n C_i^x\times \sum_{j = i}^n(-1)^{j - i}\times C_j^i\times f(j) f(x)=i=xnCix×j=in(1)ji×Cji×f(j)

f ( x ) = ∑ j = x n f ( j ) ∑ i = x j ( − 1 ) j − i × C j i × C i x f(x) = \sum_{j = x}^nf(j)\sum_{i = x}^j(-1)^{j - i}\times C_j^i\times C_i^x f(x)=j=xnf(j)i=xj(1)ji×Cji×Cix

f ( x ) = ∑ j = x n f ( j ) × C j x ∑ i = x j C j − x i − x × ( − 1 ) j − i f(x) = \sum_{j = x}^nf(j)\times C_j^x\sum_{i = x}^jC_{j - x}^{i - x}\times (-1)^{j - i} f(x)=j=xnf(j)×Cjxi=xjCjxix×(1)ji

f ( x ) = ∑ j = x n f ( j ) × C j x ∑ i = x j C j − x j − i × ( − 1 ) j − i f(x) = \sum_{j = x}^nf(j)\times C_j^x\sum_{i = x}^jC_{j - x}^{j - i}\times (-1)^{j - i} f(x)=j=xnf(j)×Cjxi=xjCjxji×(1)ji

f ( x ) = ∑ j = x n f ( j ) × C j x ∑ t = 0 j − x C j − x t × ( − 1 ) t f(x) = \sum_{j = x}^nf(j)\times C_j^x\sum_{t = 0}^{j - x}C_{j - x}^{t}\times (-1)^t f(x)=j=xnf(j)×Cjxt=0jxCjxt×(1)t

与形式 1 1 1 同理,证毕。

我们考虑这样一件事情,如果 f ( x ) f(x) f(x) 为至少 x x x 个位置满足条件的方案数, g ( x ) g(x) g(x) 为恰好 x x x 个位置满足条件的方案数,这个形式刚好和二项式反演是具有一致性的。

例题:洛谷 P4859 已经没有什么好害怕的了

给定长度为 n n n 的不重序列 a , b a,b a,b ,求将 a , b a,b a,b 任意排列,使得其中 a i > b i a_i > b_i ai>bi 的位置的个数比 b i > a i b_i > a_i bi>ai 的个数多 k k k 个的方案数, n ≤ 2000 n\leq2000 n2000

k = n + k 2 k = \frac{n+k}{2} k=2n+k

先将 a , b a,b a,b 排序。

d p i , j dp_{i,j} dpi,j a a a 的前 i i i 个元素中,有 j j j 个满足匹配的 b b b 中元素更小的条件的选择方案数,则有 d p i , j = d p i − 1 , j + d p i − 1 , j − 1 × ( n u m i − j + 1 ) dp_{i,j} = dp_{i - 1,j} + dp_{i - 1,j - 1}\times (num_i - j + 1) dpi,j=dpi1,j+dpi1,j1×(numij+1) ,其中 n u m i num_i numi 为对于第 i i i 个位置满足匹配的 b b b 中元素个数。(转移很显然,考虑第 i i i 位不匹配或匹配之前未被选过的任意一个元素)。

然后我们设 f ( x ) f(x) f(x) 为至少满足 x x x 个单调关系的方案数,则 f ( x ) = d p n , x × ( n − x ) ! f(x) = dp_{n,x} \times (n - x)! f(x)=dpn,x×(nx)! 。设 g ( x ) g(x) g(x) 为恰好满足 x x x 个单调关系的方案数。

由二项式反演,可得:
g ( k ) = ∑ i = k n ( − 1 ) i − k × C i k × f ( k ) g(k) = \sum_{i = k}^n(-1)^{i - k}\times C_i^k\times f(k) g(k)=i=kn(1)ik×Cik×f(k)

直接输出 g ( k ) g(k) g(k) 即可。

#include
#include
#include
#define MAXN 2005

using namespace std;

const int mod=1e9+9;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int a[MAXN],b[MAXN],n,k,dp[MAXN][MAXN],fac[MAXN],invfac[MAXN],f[MAXN];

int C(int n,int m){
	if(n<m) return 0;
	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}

int main(){
	cin >> n >> k;
	if(n+k&1) return cout << 0 << endl,0;
	k=n+k>>1;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
	invfac[n]=Fp(fac[n],mod-2);
	for(int i=n-1;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++) cin >> a[i]; sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) cin >> b[i]; sort(b+1,b+n+1);
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		dp[i][0]=1;
		for(int j=1;j<=i;j++){
			int rb=lower_bound(b+1,b+n+1,a[i])-b-1;
			dp[i][j]=dp[i-1][j];
			if(rb>=j) (dp[i][j]+=1ll*dp[i-1][j-1]*(rb-j+1)%mod)%=mod;
		}
	}
	for(int i=1;i<=n;i++) f[i]=1ll*dp[n][i]*fac[n-i]%mod;
	int ans=0;
	for(int i=k;i<=n;i++){
		int x=1ll*C(i,k)*f[i]%mod;
		if(i-k&1) ans=(ans-x+mod)%mod;
		else ans=(ans+x)%mod;
	}
	cout << ans << endl;
	return 0;
}

例题:洛谷 P4491 [HAOI2018] 染色
给定 n , m , s n,m,s n,m,s ,长度为 n n n 的序列, m m m 种颜色染色,求对于所有 k ∈ [ 0 , m ] k\in[ 0 , m ] k[0,m] ,恰好 k k k 种颜色出现 s s s 次的方案数。 n ≤ 1 0 7 , m ≤ 1 0 5 n\leq10^7,m\leq10^5 n107,m105

f ( i ) f(i) f(i) 为至少 i i i 种颜色出现恰好 s s s 次的方案数,则有:
f ( i ) = C m i n ! ( m − i ) n − s i s ! ( n − s i ) ! f(i) = C_m^i\frac{n!(m - i)^{n - si}}{s!(n - si)!} f(i)=Cmis!(nsi)!n!(mi)nsi

选择 i i i 种颜色,此时将整体染色方式看成一个 i + 1 i + 1 i+1 种颜色的可重集排列,再将除这 i i i 种颜色以外的颜色的方案数乘上即可。

枚举上界不重要,始终枚举到 m m m 即可。

由二项式反演,设恰好 i i i 种颜色出现恰好 s s s 次的方案数为 g ( i ) g(i) g(i) ,则有:
g ( i ) = ∑ j = i m ( − 1 ) j − i × C i j × f ( j ) g(i) = \sum_{j = i}^m(-1)^{j - i}\times C_i^j\times f(j) g(i)=j=im(1)ji×Cij×f(j)
= ∑ j = i m ( − 1 ) j − i j ! i ! ( j − i ) ! f ( j ) = \sum_{j = i}^m(-1)^{j - i}\frac{j!}{i!(j - i)!}f(j) =j=im(1)jii!(ji)!j!f(j)
= 1 i ! ∑ j = i m ( − 1 ) j − i ( j − i ) ! × f ( j ) j ! =\frac{1}{i!}\sum_{j = i}^m\frac{(-1)^{j - i}}{(j - i)!}\times f(j)j! =i!1j=im(ji)!(1)ji×f(j)j!
= 1 i ! ∑ t = 0 m ( − 1 ) t ( t ) ! f ( t + i ) ( t + i ) ! =\frac{1}{i!}\sum_{t = 0}^m\frac{(-1)^{t}}{(t)!}f(t + i)(t + i)! =i!1t=0m(t)!(1)tf(t+i)(t+i)!

A ( x ) = ( − 1 ) m − x ( m − x ) ! , B ( x ) = f ( x ) x ! A(x) = \frac{(-1)^{m - x}}{(m - x)!},B(x) = f(x)x! A(x)=(mx)!(1)mx,B(x)=f(x)x! ,第 i i i 项答案为 [ x m + i ] ( A ∗ B ) [x^{m + i}](A * B) [xm+i](AB)

注意边界条件。

#include
#include
#define MAXN 100005
#define MAXNN 10000005

using namespace std;

const int mod=1004535809;

int n,m,s,w[MAXN];

int a[MAXN<<2],b[MAXN<<2],rev[MAXN<<2],fac[MAXNN],invfac[MAXNN];

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int C(int n,int m){
	if(n<m) return 0;
	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}

void NTT(int * a,int lim,int flag){
	for(int i=0;i<lim;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int mid=1;mid<lim;mid<<=1){
		int tmp=Fp(flag==1?3:Fp(3,mod-2),(mod-1)/(mid<<1));
		for(int i=0;i<lim;i+=(mid<<1)){
			int x=1;
			for(int j=0;j<mid;j++,x=1ll*x*tmp%mod){
				int t1=a[i+j],t2=a[i+mid+j];
				a[i+j]=(t1+1ll*x*t2%mod)%mod;
				a[i+mid+j]=(t1-1ll*x*t2%mod+mod)%mod;
			}
		}
	}
	if(flag==-1){
		int inv=Fp(lim,mod-2);
		for(int i=0;i<lim;i++) a[i]=1ll*a[i]*inv%mod;
	}
}

int main(){
	cin >> n >> m >> s;
	for(int i=0;i<=m;i++) cin >> w[i];
	fac[0]=1;
	for(int i=1;i<MAXNN;i++) fac[i]=1ll*fac[i-1]*i%mod;
	invfac[MAXNN-1]=Fp(fac[MAXNN-1],mod-2);
	for(int i=MAXNN-2;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
	for(int i=0;i<=m;i++) a[i]=1ll*Fp(mod-1,m-i)*invfac[m-i]%mod;
	for(int i=0;i<=n;i++){
		if(i>m||s*i>n) break;
		b[i]=1ll*fac[i]*C(m,i)%mod*fac[n]%mod*Fp(invfac[s],i)%mod*Fp(m-i,n-s*i)%mod*invfac[n-s*i]%mod;
	}
	int lim=1,l=0;
	while(lim<=m+m+2) lim<<=1,l++;
	for(int i=0;i<lim;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
	NTT(a,lim,1); NTT(b,lim,1);
	for(int i=0;i<lim;i++) a[i]=1ll*a[i]*b[i]%mod;
	NTT(a,lim,-1);
	int ans=0;
	for(int i=0;i<=m;i++) ans=(ans+1ll*w[i]*a[m+i]%mod*invfac[i]%mod)%mod;
	cout << ans << endl;
	return 0;
}

4.拉格朗日插值

n n n 个点值可以唯一确定一个 n − 1 n - 1 n1 次多项式。

f ( k ) = ∑ i = 1 n y i ∏ j ≠ i k − x j x i − x j f(k) = \sum_{i = 1}^ny_i\prod_{j \not= i}\frac{k - x_j}{x_i - x_j} f(k)=i=1nyij=ixixjkxj

考虑模意义下的证明:
对于 n n n 个点值而言,有 f ( k ) ≡ y i (   m o d   ( k − x i ) ) f(k)\equiv y_i (\bmod (k - x_i)) f(k)yi(mod(kxi)) ,(将 f ( x ) f(x) f(x) 展开,显然可证),根据 CRT ,有:

f ( k ) = ∑ i = 1 n y i ∏ j = 1 n ( k − x j ) ∏ j ≠ i ( x i − x j ) f(k) = \sum_{i = 1}^n y_i\frac{\prod_{j = 1}^n (k - x_j)}{\prod_{j\not = i}(x_i - x_j)} f(k)=i=1nyij=i(xixj)j=1n(kxj)

整理可得。

模板题 洛谷 P4781
依照式子计算即可。

#include
#include
#define MAXN 2005

using namespace std;

const int mod=998244353;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int n,k,x[MAXN],y[MAXN];

int main(){
	cin >> n >> k;
	for(int i=1;i<=n;i++){
		cin >> x[i] >> y[i];
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int fz=y[i]%mod,fm=1;
		for(int j=1;j<=n;j++){
			if(i^j){
				fz=1ll*fz*((k-x[j]+mod)%mod)%mod;
				fm=1ll*fm*((x[i]-x[j]+mod)%mod)%mod;
			}
		}
		ans=(ans+1ll*fz*Fp(fm,mod-2))%mod;
	}
	cout << ans << endl;
	return 0;
}

例题 CF622F The Sum of the k-th Powers

∑ i = 1 n i k \sum_{i = 1}^ni^k i=1nik n ≤ 1 0 9 , k ≤ 1 0 6 n\leq10^9,k\leq10^6 n109,k106

我们不加证明地给出一个非常常见的结论, ∑ i = 1 n i k \sum_{i = 1}^ni^k i=1nik 是一个 k + 1 k + 1 k+1 次的多项式,因此,用 k + 2 k + 2 k+2 个点值就可以确定这个多项式,很显然,如果我们取 [ 1 , k + 2 ] [1,k + 2] [1,k+2] ,对于求和号里的式子是可以做到预处理后 O ( 1 ) O(1) O(1) 求的,于是整体的式子就是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,直接输出即可。

#include
#include
#define MAXN 1000005

using namespace std;

int n,k,fac[MAXN],pre[MAXN],suf[MAXN],sum[MAXN];

const int mod=1e9+7;

int Fp(int x,int tms){
	int ret=1;
	while(tms){
		if(tms&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod; tms>>=1;
	}
	return ret;
}

int main(){
	cin >> n >> k;
	fac[0]=pre[0]=1;
	for(int i=1;i<=k+2;i++){
		fac[i]=1ll*fac[i-1]*i%mod;
		pre[i]=1ll*(n-i+mod)%mod*pre[i-1]%mod;
		sum[i]=(sum[i-1]+Fp(i,k))%mod;
	}
	suf[k+3]=1;
	for(int i=k+2;i>=0;i--)
		suf[i]=1ll*(n-i+mod)%mod*suf[i+1]%mod;
	int ans=0;
	for(int i=1;i<=k+2;i++){
		int fz=sum[i],fm=1;
		fz=fz*1ll*pre[i-1]%mod*suf[i+1]%mod;
		fm=fm*1ll*fac[i-1]%mod*fac[k+2-i]%mod;
		if((k+2-i)%2==1) fm=mod-fm;
		ans=(ans+1ll*fz*Fp(fm,mod-2)%mod)%mod;
	}
	cout << ans << endl;
	return 0;
}

(证明咕了,能补就补)

你可能感兴趣的:(学习,算法,c++)