[Codeforces960G][NTT][DP]Bandit Blues

63 篇文章 0 订阅
33 篇文章 0 订阅

翻译

给你三个正整数 n,a,b,定义 A 为一个排列中是前缀最大值的数的个数,定义 B 为一个排列中是后缀最大值的数的个数,求长度为 nn 的排列中满足 A = a且 B = b 的排列个数。n≤10^5,答案对 998244353取模。

题解

很妙
我是膜beginend的!
开始想的是每次加入n+1
然后就凉了啊…
转换一下思路
每次加入最小的一个数
显然只有在加入到最前方的时候才会对前缀最大值产生贡献
f [ i ] [ j ] f[i][j] f[i][j]表示加入1~i的数,有j个前缀最大值的方案
转移就是
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + ( i − 1 ) ∗ f [ i − 1 ] [ j ] f[i][j]=f[i-1][j-1]+(i-1)*f[i-1][j] f[i][j]=f[i1][j1]+(i1)f[i1][j]
枚举最大的放哪里 可以得到
∑ i = 1 n f [ i − 1 ] [ a − 1 ] ∗ f [ n − i ] [ b − 1 ] ∗ C n − 1 i − 1 \sum_{i=1}^nf[i-1][a-1]*f[n-i][b-1]*C_{n-1}^{i-1} i=1nf[i1][a1]f[ni][b1]Cn1i1
这是个优秀的 n 2 n^2 n2方程
设前缀最大值的位置为 p 1 , p 2 , p 3... p1,p2,p3... p1,p2,p3...
可以把 [ p i , p i + 1 − 1 ] [p_i,p_{i+1}-1] [pi,pi+11]看成一组
总共有 a + b − 2 a+b-2 a+b2
选出 a − 1 a-1 a1组放到n的前面
可以知道
f [ n − 1 ] [ a + b − 2 ] ∗ C a + b − 2 a − 1 f[n-1][a+b-2]*C_{a+b-2}^{a-1} f[n1][a+b2]Ca+b2a1
然后就不会做了…
其实f的转移是第一类斯特林数的递推式
第一类斯特林数 s ( n , m ) s(n,m) s(n,m)就等于x的n次上升幂的第m项系数
x n ↑ = x ( x + 1 ) ( x + 2 ) ⋯ ( x + n − 1 ) = ∑ i = 0 n s ( n , k ) x k x^{n \uparrow} = x(x + 1)(x + 2) \cdots (x + n - 1) =\sum_{i = 0}^n s(n, k) x^k xn=x(x+1)(x+2)(x+n1)=i=0ns(n,k)xk
这个可以分治fft求出
点了两个技能真舒服

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#define mod 998244353
#define MAXN 100010
#define LL long long
#define mp(x,y) make_pair(x,y)
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
inline void print(int x){write(x);printf(" ");}
LL pow_mod(LL a,LL b)
{
	LL ret=1;
	while(b)
	{
		if(b&1)ret=ret*a%mod;
		a=a*a%mod;b>>=1;
	}
	return ret;
}
LL A[MAXN*4],B[MAXN*4];
int R[MAXN*4],L;
void NTT(LL *y,int len,int on)
{
	for(int i=0;i<len;i++)R[i]=((R[i>>1]>>1)|((i&1)*(len>>1)));
	for(int i=0;i<len;i++)if(i<R[i])swap(y[i],y[R[i]]);
	for(int i=1;i<len;i<<=1)
	{
		LL wn=pow_mod(3,(mod-1)/(i*2));if(on==-1)wn=pow_mod(wn,mod-2);
		for(int j=0;j<len;j+=(i<<1))
		{
			LL w=1;
			for(int k=0;k<i;k++)
			{
				LL u=y[j+k];
				LL v=y[j+k+i]*w%mod;
				y[j+k]=(u+v)%mod;
				y[j+k+i]=(u-v+mod)%mod;
				w=w*wn%mod;
			}
		}
	}
	if(on==-1)
	{
		LL tmp=pow_mod(len,mod-2);
		for(int i=0;i<len;i++)y[i]=(y[i]*tmp)%mod;
	}
}
void sol(LL *a,int len,int l,int r)
{
	if(l==r){a[0]=l;a[1]=1;return ;}
	int mid=(l+r)/2;LL g1[len+5],g2[len+5];
	memset(g1,0,sizeof(g1));memset(g2,0,sizeof(g2));
	sol(g1,len>>1,l,mid);sol(g2,len>>1,mid+1,r);
	
	NTT(g1,len,1);
	NTT(g2,len,1);
	for(int i=0;i<len;i++)a[i]=g1[i]*g2[i]%mod;
	NTT(a,len,-1);
}
LL pre[110000],inv[110000];
LL C(int n,int m){return pre[n]*inv[n-m]%mod*inv[m]%mod;}
int n,a,b;
int main()
{
	pre[0]=1;for(int i=1;i<=100000;i++)pre[i]=pre[i-1]*i%mod;
	inv[100000]=pow_mod(pre[100000],mod-2);
	for(int i=99999;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
	n=read();a=read();b=read();
	if(a+b-2>n-1||!a||!b){puts("0");return 0;}
	if(n==1){puts("1");return 0;}
	int ln;L=0;
	for(ln=1;ln<=2*(n+1);ln<<=1)L++;
	sol(A,ln,0,n-2);
	printf("%lld\n",A[a+b-2]*C(a+b-2,a-1)%mod);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值