CF1342E Placing Rooks
思路
首先观察到要么每一行都有车或每一列都有车,否则没有的的行与列交点的格子将不会被攻击。我们只需求到每一行都有车的放置方案数,就可以对称地得到答案。
要使恰好 \(k\) 对互相攻击,需要使用的列数为 \(n - k\),选择的方案数为 \(\binom{n}{n-k}\)。再将 \(n\) 个车分配到 \(m\) 列,方案数 \(( n - k ) ! \times S ( n , n - k )\),其中 \(S ( n , m )\) 指第二类斯特林数。
具体地,\(S ( n , m )\) 表示将 \(n\) 个不同元素分配到 \(m\) 个相同集合且保证每个集合不为空的方案数,此即将 \(n\) 个车分配到 \(n - k\) 列中去,由于每一列含义不同,故需乘上 \(( n - k ) !\) 表示对 \(n - k\) 列进行标号。
换一种角度考虑,将 \(n\) 个车分配到 \(n - k\) 的过程是一个 \(n\) 元素集合对 \(n - k\) 元素集合的满射数量。对于满射,我们定义 \(f : A \to B\) 是满射,当且仅当对 \(\forall \ y \in B,\) 存在 $ x \in A$ 使得 \(f ( x ) = y\)。
由此可以衍生出从二项式反演角度考虑的计算方法,令 \(g ( n , m )\) 是 \(n\) 元集合 \(A\) 对 \(m\) 元集合 \(B\) 的满射个数,易知由 \(A\) 到 \(B\) 的映射个数为 \(m^n\),其中使得 \(f ( A )\) 是 \(B\) 的 \(i\) 元子集的个数 为 \(\binom{m}{i} \times g ( n , i )\),故 \(m^n = \sum_{i = 1}^m \binom{m}{i} \times g ( n , i )\),由二项式反演的公式可得 \(g ( n , m ) = \sum_{i=1}^m ( -1 )^{m - i} \times \binom{m}{i} \times i^n\)。
当 \(k \neq 0\) 时,每列均有车与每行均有车的方案是一定不交的,故直接将 \(ans \times 2\) 即可完成对称计算。
下面的代码是从第二类斯特林数的角度进行的计算,显然时间复杂度 \(O ( n \log{n} )\)。
Code
#include<iostream>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int MOD=998244353;
const int N=2e5+5;
int n,k,fac[N],inv[N],A[N<<2],B[N<<2],C[N<<2],ans;
int quick_pow(int a,int b)
{
int res=1,base=a;
while(b)
{
if(b&1) res=1ll*res*base%MOD;
base=1ll*base*base%MOD;
b>>=1;
}
return res;
}
void Init(int n)
{
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=1ll*fac[i-1]*i%MOD;
inv[n]=quick_pow(fac[n],MOD-2);
for(int i=n-1;i>=0;i--)
inv[i]=1ll*inv[i+1]*(i+1)%MOD;
}
int Calc(int n,int m)
{
return 1ll*fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
struct Number_Theoretic
{
int rev[4*N],g=3;
void ntt(int a[],int n,bool invert)
{
for(int i=0;i<n;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int len=2;len<=n;len<<=1)
{
int wlen=quick_pow(g,(MOD-1)/len);
if(invert) wlen=quick_pow(wlen,MOD-2);
for(int i=0;i<n;i+=len)
{
int w=1;
for(int j=0;j<len/2;j++)
{
int u=a[i+j],v=1LL*a[i+j+len/2]*w%MOD;
a[i+j]=(u+v)%MOD;
a[i+j+len/2]=(u-v+MOD)%MOD;
w=1LL*w*wlen%MOD;
}
}
}
if(invert)
{
int inv_n=quick_pow(n,MOD-2);
for(int i=0;i<n;i++)
a[i]=1LL*a[i]*inv_n%MOD;
}
}
void multiply(int *a,int *b,int n,int m,int *res)
{
int len=1,bit=0;
while(len<n+m-1) len<<=1,bit++;
for(int i=0;i<len;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
ntt(a,len,false);
ntt(b,len,false);
for(int i=0;i<len;i++)
res[i]=1LL*a[i]*b[i]%MOD;
ntt(res,len,true);
}
}NTT;
void StirlingSecond(int n)
{
for(int i=0;i<=n;i++)
{
A[i]=1LL*quick_pow(MOD-1,i)*inv[i]%MOD;
B[i]=1LL*quick_pow(i,n)*inv[i]%MOD;
}
NTT.multiply(A,B,n+1,n+1,C);
}
int main()
{
IOS;
cin>>n>>k;
Init(n);
if(k>=n)
{
cout<<0<<'\n';
return 0;
}
if(k==0)
{
cout<<fac[n]<<'\n';
return 0;
}
StirlingSecond(n);
ans=2ll*Calc(n,k)%MOD*fac[n-k]%MOD*C[n-k]%MOD;
cout<<ans<<'\n';
return 0;
}
完结撒花~
浙公网安备 33010602011771号