【洛谷P4831】Scarlet loves WenHuaKe

题目

题目链接:https://www.luogu.com.cn/problem/P4831
Scarlet尝试在\(n\)\(m\)列的中国象棋棋盘上放置\(2\times n\) 个炮,使得它们互不攻击。
大家都知道Scarlet沉迷搞事,她想问你有多少个方案。
对于 \(45\%\) 的数据,\(n,m\leq 2000\)
对于剩余 \(55\%\) 的数据,\(n,m\leq 10^5,m-n\leq 10\)

思路

这道题完全可以加强为 \(n,m\leq 10^5\)。不需要其他限制。
显然每行必须有恰好两个棋子。所以我们可以转化为一张二分图,其中左边的点表示行,右边的点表示列。那么左右各有 \(2n\) 个点。
显然左边的两个点对应原图的一行。而右边的点可以两个组成一列,或者一个组成一列。考虑枚举右边点两个组成一列的数量 \(i\)。那么等价于原图中有 \(i\) 列有两个棋子,\(2(n-i)\) 列有一个棋子,\(m-i-2(n-i)\) 列没有棋子。
\(h_i\) 表示有 \(i\) 列有两个棋子的方案数。但是我们无法直接计算,因为拆点之后可能会重复。所以再枚举 \(j\) 表示有 \(j\) 个点计算重复了,那么有

\[h_i=\frac{1}{2^{n+i}}\sum^{i}_{j=0}(-1)^j2^jj!\binom{i}{j}\binom{n}{j}\times (2(n-i))! \]

首先任意拆了点的棋子,它的贡献都会被计算两次,而一共有 \(2^{n+i}\) 个点被拆了,所以要除以 \(2^{n+i}\)。计算重复的 \(j\) 个点与右边一共有 \(2^jj!\) 种组合方式;左右拆的点中各选出 \(j\) 各点;剩余点的匹配数有 \((2(n-i))!\) 种。
通过这一句 十 分 通 顺 的话我们可以得出 \(h_i\) 的式子。那么设

\[f_i=(-1)^i\binom{n}{i}2^i(2(n-i))! \]

\[g_i=\frac{1}{(k-i)!} \]

我们可以得到

\[h_i=\frac{i!}{2^{n+i}}\sum_{j=1}^{i}f_jg_{i-j} \]

这一步不难,直接暴力拆式子即可。
发现模数是十分可爱的 NTT 模数,直接上 NTT 即可。
最终计算答案就是在 \(m\) 列中选出 \(i\) 列放两个棋子,\(2(n-i)\) 列放一个棋子。

\[ans=\sum^{n}_{i=1}\binom{m}{i}\binom{m-i}{2(n-i)}h_i \]

时间复杂度 \(O(n\log n+m)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=300010,MOD=998244353,G=3,Ginv=332748118;
int n,m,lim,rev[N];
ll ans,f[N],g[N],fac[N],X[N],Y[N];

ll fpow(ll x,ll k)
{
	ll ans=1;
	for (;k;k>>=1,x=x*x%MOD)
		if (k&1) ans=ans*x%MOD;
	return ans;
}

ll C(int n,int m)
{
	return fac[n]*fpow(fac[m],MOD-2)%MOD*fpow(fac[n-m],MOD-2)%MOD;
}

void NTT(ll *f,int tag,int lim)
{
	for (int i=0;i<lim;i++)
		if (rev[i]<i) swap(f[rev[i]],f[i]);
	for (int k=1;k<lim;k<<=1)
	{
		ll tmp=fpow((tag==1)?G:Ginv,(MOD-1)/(k<<1));
		for (int i=0;i<lim;i+=(k<<1))
		{
			ll w=1;
			for (int j=0;j<k;j++,w=w*tmp%MOD)
			{
				ll x=f[i+j],y=w*f[i+j+k]%MOD;
				f[i+j]=(x+y)%MOD; f[i+j+k]=(x-y+MOD)%MOD; 
			}
		}
	}
}

void Fmul(ll *f,ll *g,int lim)
{
	memset(X,0,sizeof(X));
	memset(Y,0,sizeof(Y));
	for (int i=0;i<lim;i++)
		X[i]=f[i],Y[i]=g[i];
	NTT(X,1,lim); NTT(Y,1,lim);
	for (int i=0;i<lim;i++) X[i]=X[i]*Y[i]%MOD;
	NTT(X,-1,lim);
	ll inv=fpow(lim,MOD-2);
	for (int i=0;i<lim;i++) X[i]=X[i]*inv%MOD;
	memcpy(f,X,sizeof(X));
}

int main()
{
	scanf("%d%d",&n,&m);
	fac[0]=1;
	for (int i=1;i<=2*m;i++)
		fac[i]=fac[i-1]*i%MOD;
	ll p=1;
	for (int i=0;i<=n;i++,p=-p)
	{
		f[i]=p*C(n,i)*fpow(2,i)%MOD*fac[2*(n-i)]%MOD;
		g[i]=fpow(fac[i],MOD-2);
	}
	lim=1;
	while (lim<=2*n) lim<<=1;
	for (int i=0;i<lim;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)?(lim>>1):0);
	Fmul(f,g,lim);
	for (int i=0;i<=n;i++)
		if (2*n-i<=m)
		{
			f[i]=f[i]*fac[i]%MOD*fpow(2,1LL*(n+i)*(MOD-2))%MOD;
			ans=(ans+f[i]%MOD*C(m,i)%MOD*C(m-i,2*(n-i)))%MOD;
		}
	printf("%lld",ans);
	return 0;
}
posted @ 2021-01-18 14:31  stoorz  阅读(88)  评论(0编辑  收藏  举报