P7044 「MCOI-03」括号
神仙组合数学题
先考虑 0 级偏值如何计算,统计子串中不匹配的括号数即为答案,将其设为 f ( l , r , 0 ) f(l,r,0) f(l,r,0)
对于 f ( 1 , n , 0 ) f(1,n,0) f(1,n,0) 可以对于每个左括号 i i i 找到与其第一个匹配的右括号 j j j ,那么这个左括号的贡献就是 i × ( j − i ) i\times(j-i) i×(j−i) , O ( N ) O(N) O(N) 得出答案。
考虑对于
k
k
k 级偏值如何计算:
f
(
l
,
r
,
1
)
=
∑
l
′
⩾
l
r
∑
r
′
⩾
l
′
r
f
(
l
′
,
r
′
,
0
)
f
(
l
,
r
,
2
)
=
∑
l
′
⩾
l
r
∑
r
′
⩾
l
′
r
(
∑
l
′
′
⩾
l
′
r
′
∑
r
′
⩾
l
′
′
r
′
f
(
l
′
′
,
r
′
′
,
0
)
)
f
(
l
,
r
,
k
)
=
∑
l
′
⩾
l
r
∑
r
′
⩾
l
′
r
(
∑
l
′
′
⩾
l
′
r
′
∑
r
′
⩾
l
′
′
r
′
⋯
∑
∑
f
(
l
t
,
r
t
,
0
)
)
⏟
k
次
∑
l
∑
r
求
和
\begin{aligned} f(l,r,1) &=\sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}f(l',r',0)\\ f(l,r,2) &= \sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}\left(\sum_{l''\geqslant l'}^{r'}\sum_{r'\geqslant l''}^{r'}f(l'',r'',0)\right)\\ f(l,r,k) &= \underbrace{\sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}\left(\sum_{l''\geqslant l'}^{r'}\sum_{r'\geqslant l''}^{r'}\cdots \sum \sum f(lt,rt,0)\right)}_{k 次 \sum_l\sum_r 求和} \end{aligned}
f(l,r,1)f(l,r,2)f(l,r,k)=l′⩾l∑rr′⩾l′∑rf(l′,r′,0)=l′⩾l∑rr′⩾l′∑r⎝⎛l′′⩾l′∑r′r′⩾l′′∑r′f(l′′,r′′,0)⎠⎞=k次∑l∑r求和
l′⩾l∑rr′⩾l′∑r⎝⎛l′′⩾l′∑r′r′⩾l′′∑r′⋯∑∑f(lt,rt,0)⎠⎞
考虑
f
(
l
t
,
r
t
,
0
)
f(lt,rt,0)
f(lt,rt,0) 对答案的贡献,通过上式,我们可以发现,每次枚举的
l
l
l 和
r
r
r 的范围都在减小,如下图:
然后就会发现, f ( l t , r t , 0 ) f(lt,rt,0) f(lt,rt,0) 对答案的贡献次数就是
从 [ l , r ] [l,r] [l,r] 开始,每次缩小范围,令 l ⩽ l ′ ⩽ r ′ ⩽ r l\leqslant l'\leqslant r'\leqslant r l⩽l′⩽r′⩽r ,缩小 k k k 次变为 [ l t , r t ] [lt,rt] [lt,rt] 的方案数 ⇔ \Leftrightarrow ⇔ 从 [ l , l t ] [l,lt] [l,lt] 中选取 k − 1 k - 1 k−1 个单调不降的数且从 [ r t , r ] [rt,r] [rt,r] 中选取 k − 1 k-1 k−1 个单调不降的数 ⇔ \Leftrightarrow ⇔ 将 l t − l lt-l lt−l 拆分为 k k k 个自然数且将 r − r t r-rt r−rt 拆分为 k k k 个自然数的方案总数(从上图可以看出,其实就是将一段 [ l , l t ] [l,lt] [l,lt] 分成 k k k 段的方案数)
于是答案就变为
f
(
l
,
r
,
k
)
=
∑
l
⩽
l
′
⩽
r
′
⩽
r
(
l
′
−
l
+
k
−
1
k
−
1
)
(
r
−
r
′
+
k
−
1
k
−
1
)
f
(
l
′
,
r
′
,
0
)
f
(
1
,
n
,
k
)
=
∑
1
⩽
l
′
⩽
r
′
⩽
n
(
l
′
+
k
−
2
k
−
1
)
(
n
−
r
′
+
k
−
1
k
−
1
)
f
(
l
′
,
r
′
,
0
)
f(l,r,k)=\sum_{l\leqslant l'\leqslant r'\leqslant r}\binom{l'-l+k-1}{k-1}\binom{r-r'+k-1}{k-1}f(l',r',0)\\[2ex] f(1,n,k)=\sum_{1\leqslant l'\leqslant r'\leqslant n}\binom{l'+k-2}{k-1}\binom{n-r'+k-1}{k-1}f(l',r',0)
f(l,r,k)=l⩽l′⩽r′⩽r∑(k−1l′−l+k−1)(k−1r−r′+k−1)f(l′,r′,0)f(1,n,k)=1⩽l′⩽r′⩽n∑(k−1l′+k−2)(k−1n−r′+k−1)f(l′,r′,0)
枚举递增地右端点
r
r
r ,答案变为
f
(
1
,
n
,
k
)
=
(
n
−
r
′
+
k
−
1
k
−
1
)
∑
1
⩽
l
′
⩽
r
′
(
l
′
+
k
−
2
k
−
1
)
f
(
l
′
,
r
′
,
0
)
f(1,n,k)=\binom{n-r'+k-1}{k-1}\sum_{1\leqslant l'\leqslant r'}\binom{l'+k-2}{k-1}f(l',r',0)
f(1,n,k)=(k−1n−r′+k−1)1⩽l′⩽r′∑(k−1l′+k−2)f(l′,r′,0)
考虑维护
∑
1
⩽
l
′
⩽
r
′
(
l
′
+
k
−
2
k
−
1
)
f
(
l
′
,
r
′
,
0
)
\sum_{1\leqslant l'\leqslant r'}\binom{l'+k-2}{k-1}f(l',r',0)
1⩽l′⩽r′∑(k−1l′+k−2)f(l′,r′,0)
我们知道,子串的贡献一定是将所有合法括号消去后,剩下未匹配括号的数目,也就是如此形式:))...))(((..(((
,那么每当
r
r
r 向右移一位,就会有 )
或者 (
读入,此时分类讨论:
)
读入,则若当前没有未匹配的左括号,则对 [ 1 , r − 1 ] [1,r-1] [1,r−1] 中的 f f f 区间 +1,如果当前有未匹配的左括号则区间 -1.(
读入,则区间 +1.
于是考虑用线段树维护即可达到 O ( n log n ) O(n\log n) O(nlogn) 的复杂度。
当然也可以维护 ( l ′ + k − 2 k − 1 ) \binom{l'+k-2}{k-1} (k−1l′+k−2) 的前缀和或者枚举 l l l ,维护 ( n − r ′ + k − 1 k − 1 ) \binom{n-r'+k-1}{k-1} (k−1n−r′+k−1) 的后缀和,然后就能够 O ( n ) O(n) O(n) 解决问题。
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
const ll modn = 998244353;
ll n,k,prod[3000005],inv[3000005],prodinv[3000005];
ll pre[3000005],cnt1,cnt2,ans,tmp,stk[3000005],tmp2;
string s;
ll C(ll n,ll m)
{
if(n < m) return 0;
return ((prod[n]%modn*(prodinv[m]%modn))%modn*(prodinv[n - m]%modn))%modn;
}
int main()
{
scanf("%lld%lld",&n,&k);cin >> s;
if(k == 0)
{
for(int i = 1;i <= n;i ++)
{
if(s[i - 1] == '(') cnt1 ++,ans ++;
else
{
if(cnt1 ) cnt1 --,ans --;
else ans ++;
}
}
printf("%lld",(ans%modn + modn)%modn);
return 0;
}
prod[0] = 1;inv[0] = 1;inv[1] = 1;prodinv[0] = 1;
for(ll i = 1;i <= n + k + 15;i ++) prod[i] = ((prod[i - 1]%modn) * (i%modn))%modn;
for(ll i = 2;i <= n + k + 15;i ++) inv[i] = ((modn - modn / i)%modn * (inv[modn % i]%modn))%modn;
for(ll i = 1;i <= n + k + 15;i ++) prodinv[i] = ((prodinv[i - 1]%modn) * (inv[i]%modn))%modn;
for(ll i = 1;i <= n + 15;i ++) pre[i] = (pre[i - 1]%modn + C(i+k-(ll)2,k-(ll)1)%modn)%modn;
tmp = 0;
for(ll i = 1;i <= n;i ++)
{
if(s[i - 1] == '(') tmp = (tmp%modn + pre[i]%modn)%modn ,cnt1 ++,stk[cnt1] = i;
else
{
if(cnt1 > 0)
{
tmp2 = stk[cnt1];cnt1 --;//消去括号
tmp = ((tmp%modn - pre[tmp2]%modn + modn)%modn);//计算改变的值
tmp = (tmp%modn + (pre[i]%modn - pre[tmp2]%modn + modn)%modn)%modn;
}
else tmp = (tmp%modn + pre[i]%modn)%modn,cnt2 ++;
}
ans = ((ans%modn + ((tmp%modn)*(C(n-i+k-(ll)1,k-(ll)1)%modn))%modn)%modn + modn)%modn;//贡献到答案里
}
printf("%lld",(ans%modn + modn)%modn);
return 0;
}