zoukankan      html  css  js  c++  java
  • 【CF960G】Bandit Blues(第一类斯特林数,FFT)

    【CF960G】Bandit Blues(第一类斯特林数,FFT)

    题面

    洛谷
    CF
    求前缀最大值有(a)个,后缀最大值有(b)个的长度为(n)的排列个数。

    题解

    完完全全就是【FJOI】建筑师的加强版本。
    显然每一个前缀最大值和一段连续的区间构成了一个环排列,显然每个前缀最大值就是这个环中的最大值。而全局最大值一定把前后缀最大值分开。
    所以答案考虑除最大值外,左侧需要(a-1)个前缀最大值,右侧需要(b-1)个前缀最大值。也就是一共要(a+b-2)个环,那么这一部分的贡献是(egin{bmatrix}n-1\a+b-2end{bmatrix})。而环在左右随意分配,所以再乘上一个(a+b-2choose a-1)
    解释一个小问题,为什么不需要考虑环的最大值的大小关系,因为我们强制在排列过程中按照最大值从小往大放,而每个环因为放置的时候是一个线段,那么我们保证最大值一定在靠外侧,这样子后面的比它小的值必定不是前缀或者后缀最大值。

    那么问题转化成了怎么预处理第一类斯特林数。戳这里

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define MOD 998244353
    #define MAX 300000
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int r[MAX],W[MAX];
    void NTT(int *P,int opt,int N)
    {
    	int l=0;for(int i=1;i<N;i<<=1)++l;
    	for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    	for(int i=1;i<N;i<<=1)
    	{
    		int w=fpow(3,(MOD-1)/(i<<1));W[0]=1;
    		for(int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
    		for(int p=i<<1,j=0;j<N;j+=p)
    			for(int k=0;k<i;++k)
    			{
    				int X=P[j+k],Y=1ll*W[k]*P[i+j+k]%MOD;
    				P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
    			}
    	}
    	if(opt==-1)
    	{
    		reverse(&P[1],&P[N]);
    		for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    	}
    }
    int S[MAX],jc[MAX],jv[MAX],inv[MAX];
    int C(int n,int m){if(n<m)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
    int A[MAX],B[MAX],pw[MAX];
    void Solve(int len)
    {
    	if(len==0){S[0]=1;return;}
    	if(len==1){S[1]=1;return;}
    	if(len&1)
    	{
    		Solve(len-1);
    		for(int i=len;i;--i)S[i]=(S[i-1]+1ll*S[i]*(len-1))%MOD;
    	}
    	else
    	{
    		Solve(len>>1);int l=len>>1,N;
    		for(N=1;N<=len;N<<=1);
    		pw[0]=1;for(int i=1;i<=l;++i)pw[i]=1ll*pw[i-1]*l%MOD;
    		for(int i=0;i<=l;++i)A[i]=1ll*S[i]*jc[i]%MOD;
    		for(int i=0;i<=l;++i)B[i]=1ll*pw[i]*jv[i]%MOD;
    		reverse(&B[0],&B[l+1]);
    		NTT(A,1,N);NTT(B,1,N);
    		for(int i=0;i<N;++i)A[i]=1ll*A[i]*B[i]%MOD;
    		NTT(A,-1,N);
    		for(int i=0;i<=l;++i)A[i]=1ll*A[i+l]*jv[i]%MOD;
    		for(int i=l+1;i<N;++i)A[i]=B[i]=0;
    		for(int i=0;i<=l;++i)B[i]=S[i];
    		NTT(A,1,N);NTT(B,1,N);
    		for(int i=0;i<N;++i)A[i]=1ll*A[i]*B[i]%MOD;
    		NTT(A,-1,N);
    		for(int i=0;i<=len;++i)S[i]=A[i];
    		for(int i=0;i<N;++i)A[i]=B[i]=0;
    	}
    }
    int n,a,b;
    int main()
    {
    	n=read();a=read();b=read();
    	jc[0]=jv[0]=inv[0]=inv[1]=1;
    	for(int i=1;i<=max(a+b,n);++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(int i=2;i<=max(a+b,n);++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=max(a+b,n);++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    	Solve(n-1);int ans=1ll*C(a+b-2,a-1)*S[a+b-2]%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    组合算法实现
    Memcached 和 Redis 分布式锁方案
    CLR 内存分配和垃圾收集 GC
    Windbg 的使用和常用命令
    Geohash 算法学习
    经纬度计算
    Windbg 分析CPU上涨
    Windbg 分析内存上涨
    django基于存储在前端的token用户认证
    非常详细的Django使用Token(转)
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10145676.html
Copyright © 2011-2022 走看看