- 有一个长度为(n)的括号串(初始全为"(")和一个参数(k)。
- 只要(k)尚非零,不断随机一个位置将括号取反,若这次操作后左括号数量减少则将(k)减(1)。
- 求最终得到的括号串中最长合法括号子序列的期望长度。
- (n,kle5000)
含(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
",ans),0;
}