[Codeforces960G][NTT][DP]Bandit Blues

翻译

给你三个正整数 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;
}

区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值