情侣?给我烧了!(组合数学)

传送门[加强版]

传送门[普通版]

直接做加强版,改一下输出格式就能过普通版.伪双倍经验.

题意:有n对情侣来到电影院观看电影.在电影院,恰好留有n排座位,每排包含2个座位,共2×n个座位.现在,每个人将会随机坐在某一个位置上,且恰好将这2×n个座位坐满.如果一对情侣坐在了同一排的座位上,那么我们称这对情侣是和睦的.你的任务是求出共有多少种不同的就坐方案满足恰好有k对情侣是和睦的?两种就坐方案不同当且仅当存在一个人在两种方案中坐在了不同的位置.不难发现,在没有任何限制条件的情况下,每个人任意就坐一共会有(2n)!种不同的就坐方案.

分析:数论题的核心就是推出答案的表达式了.我们从简单地来,一步一步地推.首先,题目已经把n对情侣分成了k对是和睦的,n-k对是不和睦的,我们就分别来看.

k对和睦的情侣:

从n对情侣中选出k对,就是\(\tbinom{n}{k}\),从n排座位中选出k排,也是\(\tbinom{n}{k}\),每一对情侣可以坐k排中的任意一排,就有\(k!\),每一排有两个座位(对于一排座位,一对情侣有两种坐法),就有\(2^k\).所以这一部分的答案就是\(\tbinom{n}{k}^2*k!*2^k\)

n-k对不和睦的情侣(可以是同性):

高大上一点,这个就是错排问题,大可不必管.这一部分比较难想得到.既然想不到有什么直接求的公式,那就想想可不可以线性推出来.设\(f[i]\)表示有i排座位,i对情侣不和睦的方案数.

先来考虑第一排(第一次情侣不和睦的那一排),既然他们不和睦,我们就干脆把这一对拆开来一个一个地看,首先让第一个人先入座,这个人有\(2*i\)种选择,第二个人就只有\(2*i-2\)(2*i减去第一个人再减去第一个人的情侣)种选择了,所以这一部分的答案就是\(2*i*(2*i-2)\)了.因为他们坐了一排座位,所以现在只剩下i-1排座位了.

再来考虑第一排那两个人各自的情侣,显然他们有两种情况,要么干脆也坐在一起算了,要么不坐在一起.

先来讨论他们坐在一起的情况:这两个人已经固定了,没得选,但他们可以从剩下的i-1排座位中任选一排坐下,它们在一排中还可以交换位置,一旦他们坐在一排后,问题转换为了子问题:f[i-2],即剩下还有i-2对情侣不和睦的方案数.所以这一部分就是\(2*(i-1)*f[i-2]\)

再来看看他们不坐在一起的情况:既然我们现在钦定他们一定不能坐在一起,而我们现在正在大力求解的就是f[i],即i对情侣不坐在一起的方案数,所以我们干脆把他们就当做一对情侣,扔进剩下i-2对情侣中,问题相当于就转换为了子问题:f[i-1],即剩下i-1对情侣不坐在一起的方案数.所以这一部分的答案就是\(f[i-1]\)

综上,i对情侣不坐在一起的方案数就是\(f[i]=(2*i)*(2*i-2)*(f[i-1]+f[i-2]*2*(i-1))\)

注意一下f数组的初始化,f[0]=1,f[1]=0.如果有0排座位,0对情侣不坐在一起就算是一种方案.1排座位,1对情侣肯定会坐在一起,方案为零.

回顾一下本题我们所有要求解的东西:组合数\(\tbinom{n}{k}\),2的k次方,k的阶乘和f数组.组合数直接求肯定不行(n和k的阶乘肯定要边求边取模,如果取模后再相除,答案是错的,所以要用到阶乘的逆元),2的k次方可以直接预处理到一个数组中(如果每次查询时再去快速幂太慢了),k的阶乘类似于2的k次方可以直接预处理到一个数组中,f数组预处理线性递推就行.现在这些东西都可以边求边取模.

#define LL long long
#define mod 998244353
#define N 5000005
LL f[N],jc[N],inv_jc[N],pow_2[N];
LL quickpow(LL a,int b){
    LL cnt=1;
    while(b){
		if(b&1)cnt=(cnt*a)%mod;
		a=(a*a)%mod;
		b=b/2;
    }
    return cnt%mod;
}
int main(){
    f[0]=1;f[1]=0;
    for(int i=2;i<=N;++i)
    	f[i]=(f[i-1]+f[i-2]*2*(i-1))%mod*4*i%mod*(i-1)%mod;
//线性递推f数组
    jc[0]=1;
    for(int i=1;i<=N;i++) 
		jc[i]=(jc[i-1]*i)%mod; 
//预处理出jc数组
    inv_jc[N]=quickpow(jc[N],mod-2);
    for(int i=N-1;i>=0;i--) 
		inv_jc[i]=(1LL*inv_jc[i+1]*(i+1))%mod;
//预处理出阶乘的逆元
    pow_2[0]=1;
    for(int i=1;i<=N;i++)
		pow_2[i]=(1LL*pow_2[i-1]*2)%mod;
//预处理2的幂次方
    int T=read();
    while(T--){
	int n=read(),k=read();
		printf("%lld\n",jc[n]%mod*inv_jc[n-k]%mod*inv_jc[k]%mod*jc[n]%mod*inv_jc[n-k]%mod*inv_jc[k]%mod*pow_2[k]%mod*jc[k]%mod*f[n-k]%mod);
    }
    return 0;
}

posted on 2019-02-14 15:15  PPXppx  阅读(186)  评论(0编辑  收藏  举报