【洛谷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\) 个点计算重复了,那么有
首先任意拆了点的棋子,它的贡献都会被计算两次,而一共有 \(2^{n+i}\) 个点被拆了,所以要除以 \(2^{n+i}\)。计算重复的 \(j\) 个点与右边一共有 \(2^jj!\) 种组合方式;左右拆的点中各选出 \(j\) 各点;剩余点的匹配数有 \((2(n-i))!\) 种。
通过这一句 十 分 通 顺 的话我们可以得出 \(h_i\) 的式子。那么设
我们可以得到
这一步不难,直接暴力拆式子即可。
发现模数是十分可爱的 NTT 模数,直接上 NTT 即可。
最终计算答案就是在 \(m\) 列中选出 \(i\) 列放两个棋子,\(2(n-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;
}