【NOI2017模拟6.22】排列问题

4 篇文章 0 订阅
2 篇文章 0 订阅

题目大意

有n种球,每种球有不同的颜色,第i种球有a[i]个,现在将这些球排成一排,给出q组询问,每组询问给出一个数x,询问满足相邻的球颜色相同的个数为x的排列个数。
设m为所有球的个数和,数据满足: 1n,m2×105

题解

设g[i]表示所有球总共被分成i段的方案数(注意,g[i]所描述的每一段的颜色一定是一样的,但是相邻的段的颜色是可能一样的)
如果第i种球被分成了b[i]段,那么方案数就是: (bi)!(bi!)
大力分治一波+ntt就好了
设f是答案
那么,将g调换有:

gi=jifj(ij)

这个式子的意义是:如果被分成了m-i段,那么相邻的球颜色相同的个数至少为i,考虑一个相邻球颜色相同个数位j的排列在其中会被计算 (ij)
接着有
gii!=jifjj!1(ji)!

gi 调换成 gmi ,f同样
得到:
gi(mi)!=jifj(mj)!1(ij)!

上面就是一个卷积形式了
设:
g(x)=i=0mgi(mi)!xif(x)=i=0mfi(mi)!xih(x)=i=0m1i!xi

那么有:
g(x)=f(x)h(x)f(x)=g(x)h(x)

所以就要做多项式求逆,然后直接乘就好了,注意转化过程中的颠倒的问题

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<bitset>
#include<map>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;
typedef double db;

int get(){
    char ch;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    if (ch=='-'){
        int s=0;
        while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
        return -s;
    }
    int s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    return s;
}

const int MAXN = 600010;
const int MAXL = 20;
const int mo = 998244353;
const int G = 3;

int n,a[MAXN];
int st[MAXN],k;
int m;
int v[MAXN];
int A[MAXN],B[MAXN];
LL ny[MAXN],js[MAXN],e[MAXN];
int g[MAXN],f[MAXN],h[MAXN],H[MAXN];
LL d[MAXN];
int bitr[MAXN];
int N,L;
int mi[23];

LL quickmi(LL x,LL tim){
    LL ans=1;
    for(;tim;tim/=2,x=x*x%mo)
    if (tim%2)ans=ans*x%mo;
    return ans;
}

void prepare(){
    int v=quickmi(G,(mo-1)/N);
    d[0]=1;
    fo(i,1,N)d[i]=d[i-1]*v%mo;
    fo(i,0,N-1){
        bitr[i]=0;
        fo(j,0,L-1)
        if ((i&mi[j])>0)bitr[i]+=mi[L-1-j];
    }
}

int add(int x,int y){
    return x+y>=mo?x+y-mo:x+y;
}

void DFT(int *a){
    fo(i,0,N-1)
    if (i<bitr[i])swap(a[i],a[bitr[i]]);
    for(int now=2;now<=N;now<<=1){
        int half=now/2;
        fo(i,0,half-1){
            int w=d[N/now*i];
            for(int j=i;j<N;j+=now){
                int l=a[j],r=1ll*w*a[j+half]%mo;
                a[j]=add(l,r);
                a[j+half]=add(l,(mo-r)%mo);
            }
        }
    }
}

void IDFT(int *a){
    fo(i,0,N-1)
    if (i<bitr[i])swap(a[i],a[bitr[i]]);
    for(int now=2;now<=N;now<<=1){
        int half=now/2;
        fo(i,0,half-1){
            int w=d[N-N/now*i];
            for(int j=i;j<N;j+=now){
                int l=a[j],r=1ll*w*a[j+half]%mo;
                a[j]=add(l,r);
                a[j+half]=add(l,(mo-r)%mo);
            }
        }
    }
    LL tmp=quickmi(N,mo-2);
    fo(i,0,N-1)a[i]=tmp*a[i]%mo;
}

void get_nv(int *f,int *f0,int len){
    if (len==1){
        f0[0]=quickmi(f[0],mo-2);
        return;
    }
    get_nv(f,f0,(len+1)/2);
    N=1;L=0;
    while(N<=2*len){N<<=1;L++;}
    prepare();
    fo(i,0,N-1)A[i]=B[i]=0;
    fo(i,0,(len+1)/2-1)B[i]=f0[i];
    fo(i,0,len-1)A[i]=f[i];
    DFT(A);
    DFT(B);
    fo(i,0,N-1)A[i]=1ll*B[i]*(2ll+mo-1ll*A[i]*B[i]%mo)%mo;
    IDFT(A);
    fo(i,0,len-1)f0[i]=A[i];
}

int main(){
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
    mi[0]=1;
    fo(i,1,20)mi[i]=mi[i-1]<<1;
    n=get();
    js[0]=ny[0]=1;
    fo(i,1,200000)js[i]=js[i-1]*i%mo;
    ny[200000]=quickmi(js[200000],mo-2);
    fd(i,199999,1)ny[i]=ny[i+1]*(i+1)%mo;
    fo(i,1,200000)e[i]=ny[i]*js[i-1]%mo;
    fo(i,1,n){
        st[++k]=m+1;
        m+=(a[i]=get())+1;
        LL now=1;
        fo(j,0,a[i]-1){
            v[st[k]+j+1]=now*ny[j+1]%mo;
            now=now*e[j+1]%mo*(a[i]-1-j)%mo;
        }
    }
    for(N=1,L=0;k>1;N<<=1,L++){
        prepare();
        int k_=k;
        k=0;
        for(int now=1,w=1;now<=k_;now=w+1){
            if ((now<k_&&a[now]+a[now+1]>=N)||(now==k_)){
                st[++k]=st[now];
                a[k]=a[now];
                w=now;
                continue;
            }
            w=now+1;
            fo(i,0,a[now])A[i]=v[st[now]+i];
            fo(i,0,a[w])B[i]=v[st[w]+i];
            DFT(A);
            DFT(B);
            fo(i,0,N-1)A[i]=1ll*A[i]*B[i]%mo;
            IDFT(A);
            fo(i,0,a[now]+a[w])v[st[now]+i]=A[i];
            fo(i,0,N-1)A[i]=B[i]=0;
            st[++k]=st[now];
            a[k]=a[now]+a[w];
        }
    }
    m-=n;
    fo(i,0,m)g[i]=1ll*v[i+1]*js[m-i]%mo*js[i]%mo;
    fo(i,0,m)h[i]=ny[i];
    get_nv(h,H,m+1);
    if (N<=2*m){
        N<<=1;L++;
        prepare();
    }
    DFT(g);
    DFT(H);
    fo(i,0,N-1)f[i]=1ll*g[i]*H[i]%mo;
    IDFT(f);
    fo(i,0,m)f[i]=1ll*f[i]*ny[m-i]%mo;
    fo(i,0,m/2)swap(f[i],f[m-i]);
    for(int q=get();q;q--){
        int x=get();
        if (x>m)printf("0\n");
        else printf("%d\n",f[x]);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值