P3059
转前缀和
q
q
q,考虑如何判断一个子串
[
l
,
r
]
[l,r]
[l,r] 合法,发现一个充分非必要条件是
q
l
=
q
r
q_l=q_r
ql=qr,再考虑完备性,当
(
l
,
r
)
(l,r)
(l,r) 中每一个
q
i
q_i
qi 均小于
q
l
q_l
ql 时完备。完备性证明显然,左括号数量始终
>
=
>=
>= 右括号数量。
那么基本思路就有了,用 hash 记录下一个相同的 hash 值,设
d
p
i
dp_i
dpi 表示以
i
i
i 为起点的满足题意的子串数量,则有
d
p
i
=
d
p
n
t
+
1
+
1
dp_i=dp_{nt+1}+1
dpi=dpnt+1+1,
n
t
nt
nt 是
i
i
i 开头最短的合法子串的右端点。
问题只剩下如何求右端点位置,根据第一段的两个需要同时成立的条件,设每一列取 hash 后的序列为
q
′
q'
q′,设
r
r
r 为
l
l
l 向右第一个满足
q
l
′
=
q
r
′
q'_l=q'_r
ql′=qr′ 的位置,则当且仅当
r
<
min
(
每个串以
l
为起点向右第一个小于
q
l
的位置
)
)
r<\min(每个串以 l 为起点向右第一个小于 q_l的位置))
r<min(每个串以l为起点向右第一个小于ql的位置))时,
n
t
=
r
nt=r
nt=r,否则
n
t
nt
nt 不存在。
不难想到
r
r
r 的求法:从右向左扫描,动态更新桶和
r
r
r 序列。
那每个串以 l 为起点向右第一个小于
q
l
q_l
ql 的位置怎么求?第一想法是树状数组或st表,可行。不过根据括号序列的性质,
q
q
q 序列在预处理过程中只存在+1和-1的情况,所以要求的这个位置
p
o
s
pos
pos 必满足
q
p
o
s
=
q
l
−
1
q_{pos}=q_l-1
qpos=ql−1,因此用求
r
r
r 的方法即可快速求
p
o
s
pos
pos。
最后发现所有的求解都要求先知道右侧的值,所以一个循环从右向左遍历即可,码量亲和。
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int k,n,q[15][N],f[N],t,mn[15][N<<1];
char s[15][N];
map<int,int> mp;
int main(){
scanf("%d%d",&k,&n);
for(int i=1;i<=k;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=n;j++) q[i][j]=q[i][j-1]+(s[i][j]^')'?1:-1);
}
memset(mn,0x3f,sizeof(mn));
for(int i=n+1;i;i--){
int x=0,l=n+1;
for(int j=1;j<=k;j++){
mn[j][q[j][i]+N]=i;l=min(l,mn[j][q[j][i-1]+N-1]);
x=x*131+q[j][i-1];
}
if(mp.count(x)&&mp[x]<=l) f[i]=f[mp[x]]+1;t+=f[i];
mp[x]=i;
}
printf("%d",t);
}
降低码量时多了一些细节,不影响阅读。