把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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

点此看题面

  • 求有多少长度为\(n\)的排列,满足它有\(A\)个前缀最大值和\(B\)个后缀最大值。
  • \(n\le10^5\)

第一类斯特林数

显然,序列中的最大值必然既是前缀最大值又是后缀最大值,而它左右区间的数则形成了相对独立的两部分。

我们把每个前缀最大值及它到下一个前缀最大值之前的数看成一部分,每个后缀最大值及它到上一个后缀最大值之前的数也看成一部分。

后者可以认为是由前者翻转得到的,所以这两种情况应该可以类似讨论。

然后我们就发现除去最大值之后剩余的\(n-1\)个数被我们划分成了\(a+b-2\)个部分,每个部分可自己任定相对顺序,但又必须以这一部分的最大值作为开头。

所以说,每一部分可以看成一个环,那么方案数就应该是\(S_1(n-1,a+b-2)\)

而左右两块中,每一块的环都必须要满足最大值递增,相对顺序是固定的,因此只要考虑把这些环分成两部分的方案数,也就是\(C_{a+b-2}^{a-1}\)

综上,答案就应该是:

\[S_1(n-1,a+b-2)\times C_{a+b-2}^{a-1} \]

快速求斯特林数(伪)

只会\(O(nlog^2n)\)的屑做法,但也足以通过此题了。。。

考虑第一类斯特林数的递推式:

\[S_1(n,m)=S_1(n-1,m-1)+S_1(n-1,m)\times(n-1) \]

我们把每一行的斯特林数看成一个多项式(第\(n\)行用\(S_1[n]\)来表示),那么上式就可以写成:

\[S_{1}[n]=S_1[n-1]\times(x+n-1) \]

简单地说,这也就是:

\[S_1[n]=\prod_{i=1}^n(x+i-1) \]

显然这个式子可以直接分治\(NTT\),那么就结束了。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define X 998244353
using namespace std;
int n,A,B;I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
namespace Poly
{
	#define PR 3
	int P,L,A[N<<1],B[N<<1],R[N<<1];I void T(int* s,CI op)
	{
		RI i,j,k,x,y,U,S;for(i=0;i^P;++i) i<R[i]&&(swap(s[i],s[R[i]]),0);
		for(i=1;i^P;i<<=1) for(U=QP(QP(PR,op),(X-1)/(i<<1)),j=0;j^P;j+=i<<1) for(S=1,
			k=0;k^i;++k,S=1LL*S*U%X) s[j+k]=((x=s[j+k])+(y=1LL*S*s[i+j+k]%X))%X,s[i+j+k]=(x-y+X)%X;
	}
	int ct,f[50][N+5];I void Solve(CI l,CI r,CI rt)//分治NTT
	{
		if(l==r) return (void)(f[rt][0]=l-1,f[rt][1]=1);//边界
		RI i,mid=l+r>>1,lc=++ct,rc=++ct;Solve(l,mid,lc),Solve(mid+1,r,rc);//递归
		P=1,L=0;W(P<=2*(r-l+1)) P<<=1,++L;for(i=0;i^P;++i) A[i]=B[i]=0,R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
		for(i=0;i<=mid-l+1;++i) A[i]=f[lc][i];for(i=0;i<=r-mid;++i) B[i]=f[rc][i];
		for(T(A,1),T(B,1),i=0;i^P;++i) A[i]=1LL*A[i]*B[i]%X;//把左右区间的多项式卷起来
		RI t=QP(P,X-2);for(T(A,X-2),i=0;i<=r-l+1;++i) f[rt][i]=1LL*A[i]*t%X;ct-=2;
	}
}
I int S1(CI n,CI m) {return n>m?(Poly::Solve(1,n,0),Poly::f[0][m]):n==m;}//第一类斯特林数
I int C(CI n,CI m) {RI i,t=1;for(i=m+1;i<=n;++i) t=1LL*t*i%X*QP(i-m,X-2)%X;return t;}//组合数
int main()
{
	if(scanf("%d%d%d",&n,&A,&B),!A||!B) return puts("0"),0;//不可能为0,因为整个序列的最大值必然是前后缀最大值
	return printf("%lld\n",1LL*S1(n-1,A+B-2)*C(A+B-2,A-1)%X),0;//根据答案式计算
}
posted @ 2021-03-26 09:53  TheLostWeak  阅读(61)  评论(0编辑  收藏  举报