2019.03.29【洛谷P4931】情侣?给我烧了!(加强版)(组合数学)(生成函数)

传送门

顺便把弱化版一起切了:P4921


解析:

这种数数问题还是考虑分部分计算。

一,匹配的k对

这部分很简单,选出 k k k个位置上火刑柱 ( n k ) n\choose k (kn),选出 k k k送到FFF大本营 ( n k ) n\choose k (kn),在 k k k个位置里面选择 k ! k! k!,每一对两个人位置可以互换 2 k 2^k 2k

所以这一部分的计算就是 ( n k ) 2 k ! 2 k {n\choose k}^2k!2^k (kn)2k!2k

二,不能匹配的n-k对

显然现在剩下的位置确定了,剩下的情侣也确定了。

也就是说,我们要求的就是 n − k n-k nk对情侣全部错开的方案数。

d n d_n dn表示 n n n对情侣全部错开的方案数。

接下来有两种方法求出 { d } \{d\} {d}数列的递推式,我第一次做的时候用的是第二种方法。

(1)组合分析

在第一排的两个位置放两个不是情侣的人,显然方案数是 2 n ( 2 n − 2 ) 2n(2n-2) 2n(2n2)

然后看这两个人的另一半,我们对这两个人的限制分情况讨论。

1.强制不坐在一起

那么相当于这两个人形成了新的情侣 (大自然的力量) ,直接从子问题 d n − 1 d_{n-1} dn1转移过来。

2.强制坐在一起

在剩下 n − 1 n-1 n1排中任意选择一排,两人位置允许互换,子问题转化为 d n − 2 d_{n-2} dn2

综上,我们得到递推式: d n = 4 n ( n − 1 ) ( d n − 1 + 2 ( n − 1 ) d n − 2 ) d_n=4n(n-1)(d_{n-1}+2(n-1)d_{n-2}) dn=4n(n1)(dn1+2(n1)dn2)

(2)生成函数分析

这种方法较为复杂,建议数学(微积分)基础较好再来尝试。

考虑配对对数为 [ 0 , n ] [0,n] [0,n]的所有情况可以得到一个组合恒等式: ∑ k = 0 n ( n k ) 2 k ! 2 k d n − k = ( 2 n ) ! \sum_{k=0}^n{n\choose k}^2k!2^kd_{n-k}=(2n)! k=0n(kn)2k!2kdnk=(2n)!

开始化简:
∑ k = 0 n ( n k ) 2 k ! 2 k d n − k = ( 2 n ) ! ∑ k = 0 n 2 k d n − k k ! ( n − k ) ! 2 = ( 2 n ) ! n ! 2 \begin{aligned} \sum_{k=0}^n{n\choose k}^2k!2^kd_{n-k}&=&&(2n)!\\ \sum_{k=0}^n\frac{2^kd_{n-k}}{k!(n-k)!^2}&=&&\frac{(2n)!}{n!^2} \end{aligned} k=0n(kn)2k!2kdnkk=0nk!(nk)!22kdnk==(2n)!n!2(2n)!

好像是一个卷积的形式,两边进行 ∑ n = 0 ∞ \sum\limits_{n=0}^{\infty} n=0的求和,强行转化成EGF。

( ∑ n = 0 ∞ 2 n n ! x n ) ( ∑ n = 0 ∞ d n n ! 2 x n ) = ∑ n = 0 ∞ ( 2 n ) ! n ! 2 x n \begin{aligned} (\sum_{n=0}^{\infty}\frac{2^n}{n!}x^n)(\sum_{n=0}^{\infty}\frac{d_n}{n!^2}x^n)=\sum_{n=0}^{\infty}\frac{(2n)!}{n!^2}x^n \end{aligned} (n=0n!2nxn)(n=0n!2dnxn)=n=0n!2(2n)!xn

根据泰勒展开,我们可以知道 ∑ n = 0 ∞ ( 2 n ) ! n ! 2 x n = ( 1 − 4 x ) − 1 2 \sum_{n=0}^{\infty}\frac{(2n)!}{n!^2}x^n=(1-4x)^{-\frac{1}2} n=0n!2(2n)!xn=(14x)21
∑ n = 0 ∞ 2 n n ! x n = e 2 x \sum_{n=0}^{\infty}\frac{2^n}{n!}x^n=e^{2x} n=0n!2nxn=e2x

D ( x ) = ∑ n = 0 ∞ d n n ! 2 x n D(x)=\sum\limits_{n=0}^{\infty}\frac{d_n}{n!^2}x^n D(x)=n=0n!2dnxn,则有

D ( x ) = e − 2 x 1 − 4 x D(x)=\frac{e^{-2x}}{\sqrt{1-4x}} D(x)=14x e2x

当然如果你硬是要算通项公式的话,直接用 e − 2 x e^{-2x} e2x展开形式和 ∑ n = 0 ∞ ( 2 n ) ! n ! x n \sum_{n=0}^{\infty}\frac{(2n)!}{n!}x^n n=0n!(2n)!xn做卷积可以得到

d n = ∑ k = 0 n ( n k ) 2 ( − 2 ) k k ! ( 2 n − 2 k ) ! d_n=\sum_{k=0}^n{n\choose k}^2(-2)^kk!(2n-2k)! dn=k=0n(kn)2(2)kk!(2n2k)!

发现又是一个卷积,可以用 F F T O ( n log ⁡ n ) FFTO(n\log n) FFTO(nlogn)做了,但是 5 e 6 5e6 5e6就不要想了。。。

D ( x ) D(x) D(x)求导得到 D ′ ( x ) = 8 x ⋅ e − 2 x ( 1 − 4 x ) 3 2 = 8 x 1 − 4 x D ( x ) D^\prime(x)=\frac{8x\cdot e^{-2x}}{(1-4x)^{\frac{3}2}}=\frac{8x}{1-4x}D(x) D(x)=(14x)238xe2x=14x8xD(x)
( 1 − 4 x ) D ′ ( x ) = 8 x D ( x ) (1-4x)D^\prime(x)=8xD(x) (14x)D(x)=8xD(x)

对应项还原系数可以得到:

d n = 4 n ( n − 1 ) ( d n − 1 + 2 ( n − 1 ) d n − 2 ) d_n=4n(n-1)(d_{n-1}+2(n-1)d_{n-2}) dn=4n(n1)(dn1+2(n1)dn2)

总算写完了


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int sqr(int a){return (ll)a*a%mod;}
template<class ...Args>
inline int mul(int a,Args ...args){
	return mul(a,mul(args...));
}

cs int N=5e6+6;
int fac[N],inv[N],ifac[N],pow2[N];
int d[N];

inline int C(int n,int m){
	return mul(fac[n],mul(ifac[m],ifac[n-m]));
}

int T,n,k;
signed main(){
	fac[0]=fac[1]=ifac[0]=ifac[1]=inv[0]=inv[1]=pow2[0]=d[0]=1;pow2[1]=2;
	for(int re i=2;i<N;++i){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(inv[mod%i],mod-mod/i);
		ifac[i]=mul(ifac[i-1],inv[i]);
		pow2[i]=add(pow2[i-1],pow2[i-1]);
		d[i]=mul(4,i,i-1,add(d[i-1],mul(2,i-1,d[i-2])));
	}
	std::ios::sync_with_stdio(false);
	T=getint();
	while(T--){
		n=getint(),k=getint();
		std::cout<<mul(sqr(C(n,k)),fac[k],pow2[k],d[n-k])<<"\n";
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值