【洛谷6689】序列(组合数学)

点此看题面

  • 有一个长度为\(n\)的括号串(初始全为"(")和一个参数\(k\)。
  • 只要\(k\)尚非零,不断随机一个位置将括号取反,若这次操作后左括号数量减少则将\(k\)减\(1\)。
  • 求最终得到的括号串中最长合法括号子序列的期望长度。
  • \(n,k\le5000\)

含\(i\)个右括号的序列产生概率

设\(f_{i,j}\)表示当前\(k=i\),右括号个数为\(j\)的概率。

用刷表法转移,有\(\frac jn\)的概率转移到\(f_{i,j-1}\),有\(\frac{n-j}n\)的概率转移到\(f_{i-1,j+1}\)。

由于含\(i\)个右括号的序列共有\(C_n^i\)个,所以我们最终令\(g_i=\frac{f_{0,i}}{C_n^i}\),这就是一种含\(i\)个右括号的序列产生的概率。

最长合法括号子序列

考虑最长合法括号子序列的转化。

把"("视作\(1\),")"视作\(-1\),然后记录\(s_i\)表示前\(i\)项的前缀和。

设\(x\)为\(s_i\)最小值所在的位置,则在此之前有\(-s_x\)个右括号无法匹配,在此之后有\(s_n-s_x\)个左括号无法匹配。

用括号总数减去无法匹配括号数,得到最长合法括号子序列长度为\(n-s_n+2s_x\)。

然后我们每次要求前缀和不小于某个数的方案数,典型的坐标系走路问题,直接折线法计算即可。

代码:\(O(nk+n^2)\)

#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 5000
#define X 998244353
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)//组合数
using namespace std;
int n,k,f[N+5][N+5],g[N+5];
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;}
int Fac[N+5],IFac[N+5];I void InitFac(CI S)//预处理阶乘和阶乘逆元
{
	RI i;for(Fac[0]=i=1;i<=S;++i) Fac[i]=1LL*Fac[i-1]*i%X;
	for(IFac[i=S]=QP(Fac[S],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X; 
}
I int Calc(CI m) {return (C(n,m)-C(n,m-1)+X)%X;}//折线法计算方案数
int main()
{
	RI i,j;scanf("%d%d",&n,&k),InitFac(n);
	RI Inv=QP(n,X-2);for(f[k][0]=1,i=k;i;--i) for(j=n;~j;--j)//DP转移求f
		f[i-1][j+1]=(1LL*f[i][j]*(n-j)%X*Inv+f[i-1][j+1])%X,j&&(f[i][j-1]=(1LL*f[i][j]*j%X*Inv+f[i][j-1])%X);//随到左括号/右括号
	for(i=0;i<=n;++i) g[i]=1LL*f[0][i]*QP(C(n,i),X-2)%X;//记录一种含i个右括号的序列产生的概率
	RI ans=0;for(i=1;i<=n;++i) for(j=1;j<=min(i,n-i);++j) ans=(2LL*j*g[i]%X*Calc(j)+ans)%X;//利用结论计算
	return printf("%d\n",ans),0;
}
上一篇:Redis(三):多机数据库的实现


下一篇:【洛谷4721】【模板】分治 FFT