[XSY 2666][分治FFT][容斥]排列问题

本文探讨了一种解决排列问题的方法,通过计算不同颜色段数的方案数,并利用分治策略与快速傅里叶变换(FFT)进行优化。在不允许相同颜色相邻的情况下,通过容斥原理得到最终答案,通过数学归纳法证明了贡献公式,并利用数位转换技术(NTT)加速计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
在这里插入图片描述
yy一个方案有多少对相邻的相同,显然是m-颜色段数
我们令fi,jf_{i,j}fi,j表示前i种颜色分成j段颜色段数的方案数。
注意,我们这里允许两个同样颜色的颜色段相邻。
可以得到转移方程fi,j=∑k=0j−1fi−1,k∗(ai−1j−k−1)∗1j−kf_{i,j}=\sum_{k=0}^{j-1}f_{i-1,k}*\binom{a_{i}-1}{j-k-1}*\frac{1}{j-k}fi,j=k=0j1fi1,k(jk1ai1)jk1
可以分治FFT完成。
当然,这求出来的不是真正的f。
我们需要执行fn,i=i!∗fn,i(iϵ[1,m])f_{n,i}=i!*f_{n,i}(i\epsilon [1,m])fn,i=i!fn,i(iϵ[1,m])
不妨令gi=fn,ig_{i}=f_{n,i}gi=fn,i,ansians_{i}ansi为分成i段的真正答案(即不允许有同样颜色的颜色段相邻)
可以得到容斥式子:
ansi=gi−∑j=1i−1ansj∗(m−ji−j)ans_{i}=g_{i}-\sum_{j=1}^{i-1}ans_{j}*\binom{m-j}{i-j}ansi=gij=1i1ansj(ijmj)
考虑继续化简。
可以发现,gjg_{j}gjansians_{i}ansi的贡献为(−1)i−j∗(m−ji−j)(-1)^{i-j}*\binom{m-j}{i-j}(1)ij(ijmj)
证明如下(采用归纳证明):
gk−>ansig_{k}->ans_{i}gk>ansi
=∑j=ki−1(−1)j−k+1∗(m−kj−k)∗(m−ji−j)=\sum_{j=k}^{i-1}(-1)^{j-k+1}*\binom{m-k}{j-k}*\binom{m-j}{i-j}=j=ki1(1)jk+1(jkmk)(ijmj)
=∑j=ki−1(−1)j−k+1∗(m−k)!(m−j)!(j−k)!∗(m−j)!(m−i)!(i−j)!=\sum_{j=k}^{i-1}(-1)^{j-k+1}*\frac{(m-k)!}{(m-j)!(j-k)!}*\frac{(m-j)!}{(m-i)!(i-j)!}=j=ki1(1)jk+1(mj)!(jk)!(mk)!(mi)!(ij)!(mj)!
=∑j=ki−1(−1)j−k+1∗(m−k)!(j−k)!(m−i)!(i−j)!=\sum_{j=k}^{i-1}(-1)^{j-k+1}*\frac{(m-k)!}{(j-k)!(m-i)!(i-j)!}=j=ki1(1)jk+1(jk)!(mi)!(ij)!(mk)!
=(m−k)!(m−i)!(i−k)!∑j=ki−1(−1)j−k+1∗(i−k)!(i−j)!(j−k)!=\frac{(m-k)!}{(m-i)!(i-k)!}\sum_{j=k}^{i-1}(-1)^{j-k+1}*\frac{(i-k)!}{(i-j)!(j-k)!}=(mi)!(ik)!(mk)!j=ki1(1)jk+1(ij)!(jk)!(ik)!
=(m−ki−k)∑j=ki−1(−1)j−k+1∗(i−kj−k)=\binom{m-k}{i-k} \sum_{j=k}^{i-1}(-1)^{j-k+1}*\binom{i-k}{j-k}=(ikmk)j=ki1(1)jk+1(jkik)
=(−1)i−k∗(m−ki−k)=(-1)^{i-k}*\binom{m-k}{i-k}=(1)ik(ikmk)
来次NTT即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int Mod=998244353;
int n,m,q;
#define Maxn 200010
int a[Maxn];
int fact[Maxn],inv[Maxn];
inline int C(int i,int j){return 1ll*fact[i]*inv[i-j]%Mod*inv[j]%Mod;}

int A[Maxn<<2],B[Maxn<<2];
int rev[Maxn<<2],len,bit;
inline int FP(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*ans*a%Mod;
        a=1ll*a*a%Mod;
        b>>=1;
    }
    return ans;
}
inline void NTT(int *A,int t){
    for(int i=0;i<len;++i)
        if(i<rev[i])swap(A[i],A[rev[i]]);
    for(int i=1;i<len;i<<=1){
        int gn=FP(3,(t*(Mod-1)/(i<<1)+(Mod-1))%(Mod-1));
        for(int j=0;j<len;j+=i<<1){
            int g=1;
            for(int k=0;k<i;++k){
                int x=A[j+k];
                int y=1ll*g*A[j+k+i]%Mod;
                A[j+k]=(x+y)%Mod;
                A[j+k+i]=(x-y+Mod)%Mod;
                g=1ll*g*gn%Mod;
            }
        }
    }
    if(t==-1){
        int Inv=FP(len,Mod-2);
        for(int i=0;i<len;++i)A[i]=1ll*A[i]*Inv%Mod;
    }
}
inline void Mul(int *A,int *B){
    NTT(A,1);NTT(B,1);
    for(int i=0;i<len;++i)A[i]=1ll*A[i]*B[i]%Mod;
    NTT(A,-1);
}

vector<int> poly[Maxn<<2];
void solve(int k,int l,int r){
    if(l==r){
        poly[k].push_back(0);
        for(int i=1;i<=a[l];++i)poly[k].push_back(1ll*C(a[l]-1,i-1)*inv[i]%Mod);
        return;
    }
    int mid=(l+r)>>1;
    solve(k<<1,l,mid);
    solve(k<<1|1,mid+1,r);
    int l1=poly[k<<1].size()-1,l2=poly[k<<1|1].size()-1;
    for(int i=0;i<=l1;++i)A[i]=poly[k<<1][i];
    for(int i=0;i<=l2;++i)B[i]=poly[k<<1|1][i];
    len=1;bit=0;
    while(len<=l1+l2)len<<=1,bit++;
    for(int i=l1+1;i<len;++i)A[i]=0;
    for(int i=l2+1;i<len;++i)B[i]=0;
    for(int i=0;i<len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    Mul(A,B);
    for(int i=0;i<=l1+l2;++i)poly[k].push_back(A[i]);
}

inline void rd(int &x){
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
}

int main(){
    rd(n);m=0;
    for(register int i=1;i<=n;++i){
        rd(a[i]);
        m+=a[i];
    }
    fact[0]=1;
    for(register int i=1;i<=m;++i)fact[i]=1ll*fact[i-1]*i%Mod;
    inv[0]=inv[1]=1;
    for(register int i=2;i<=m;++i)inv[i]=1ll*(Mod-Mod/i)*inv[Mod%i]%Mod;
    for(register int i=2;i<=m;++i)inv[i]=1ll*inv[i-1]*inv[i]%Mod;
    solve(1,1,n);
    for(register int i=0;i<=m;++i)A[i]=1ll*A[i]*fact[i]%Mod*fact[m-i]%Mod;
    for(register int i=0;i<=m;++i){
        if(i&1)B[i]=Mod-inv[i];
        else B[i]=inv[i];
    }
    len=1;bit=0;
    while(len<=2*m)len<<=1,bit++;
    for(register int i=0;i<=len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    Mul(A,B);
    for(register int i=1;i<=m;++i)A[i]=1ll*A[i]*inv[m-i]%Mod;
    rd(q);
    int x;
    while(q--){
        rd(x);
        printf("%d\n",A[m-x]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值