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;
}

完结撒花~

posted @ 2025-11-06 15:50  FallingGardenia  阅读(2)  评论(0)    收藏  举报