- 给定一个括号串,其中有一些位置可以任填左右括号中的一个。
- 求所有方案下,串中合法括号匹配的最大深度之和。
- (nle10^6)
寻找唯一性
要求合法括号匹配的最大深度,看起来令人非常懵逼。
实际上,我们枚举一个间隙,那么以它为划分点的最大深度就是它左侧左括号数与右侧右括号数的较大值。
然后考虑什么时候能取到最大深度,发现当且仅当左侧左括号数等于右侧右括号数。
由于无论向左还是向右移一步都会导致括号数发生变化,因此这样的划分点是唯一的。
所以我们只要枚举每个间隙,求出它作为最优划分点时的贡献之和即可。
组合数学
假设当前左边有(cl)个(
,右边有(cr)个)
,并假设两边分别有(tl,tr)个?
。
枚举左边有(i)个?
填成了(
,那么右边就需要有恰好(cl+i-cr)个?
填成了)
,且此时对答案的贡献是(cl+i)。
因此列出计算式:
[sum_{i=0}^{tl}(cl+i)C_{tl}^iC_{tr}^{cl+i-cr}
]
把它拆成两部分分别计算贡献:
[clsum_{i=0}^{tl}C_{tl}^iC_{tr}^{cl+i-cr}+sum_{i=0}^{tl}iC_{tl}^iC_{tr}^{cl+i-cr}
]
对于左边,首先把(C_{tr}^{cl+i-tr})上方反一下,得到:
[clsum_{i=0}^{tl}C_{tl}^iC_{tr}^{tr+cr-cl-i}
]
这样一来,好处就是两个组合数中的(i+(tr+cr-cl-i)=tr+cr-cl)是个定值,而(i)又是我们正在枚举的量,因此可以考虑组合意义直接把它们合并,就相当于:
[cl imes C_{tl+tr}^{tr+cr-cl}
]
右边其实也是一样的,就是需要先把(i)给搞到组合数里面:
[sum_{i=0}^{tl}tlC_{tl-1}^{i-1}C_{tr}^{cl+i-cr}
]
剩下的都是类似的:
[tlsum_{i=0}^{tl}C_{tl-1}^{i-1}C_{tr}^{tr+cr-cl-i}\
tl imes C_{tl+tr-1}^{tr+cr-cl-1}
]
也就是说,这个划分位置的贡献就是:
[cl imes C_{tl+tr}^{tr+cr-cl}+tl imes C_{tl+tr-1}^{tr+cr-cl-1}
]
显然可以直接计算。
代码:(O(n))
#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 1000000
#define X 998244353
#define C(x,y) (0<=(y)&&(y)<=(x)?1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X:0)
using namespace std;
int n;char s[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()//预处理阶乘和阶乘逆元
{
RI i;for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;
for(IFac[i=n]=QP(Fac[n],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
}
int main()
{
RI i;scanf("%s",s+1),n=strlen(s+1),InitFac();
RI cl=0,cr=0,tl=0,tr=0;for(i=1;i<=n;++i) s[i]==')'&&++cr,s[i]=='?'&&++tr;//初始全在右半部分
RI t=0;for(i=1;i^n;++i) s[i]=='('&&++cl,s[i]==')'&&--cr,//更新左半部分左括号数和右半部分有括号数
s[i]=='?'&&(++tl,--tr),t=(t+1LL*cl*C(tl+tr,tr+cr-cl)+1LL*tl*C(tl+tr-1,tr+cr-cl-1))%X;//更新两边问号数,计算当前贡献作为最优划分点的贡献
return printf("%d
",t),0;
}