分析:
部分分写一个链剖
线段树中维护信息:
sum
s
u
m
:区间和(“(”赋值为1,“)”赋值为-1)
lmx
l
m
x
:从左端开始的区间最大值
rmx
r
m
x
:从右端开始的区间最大值
lmn
l
m
n
:从左端开始的区间最小值
rmn
r
m
n
:从右端开始的区间最小值
L
L
:未匹配的左括号
:未匹配的右括号
简单看一下核心代码:
Tree update(Tree l,Tree r) {
Tree ans;
ans.sum=l.sum+r.sum;
ans.lmn=min(l.lmn,l.sum+r.lmn);
ans.lmx=max(l.lmx,l.sum+r.lmx);
ans.rmn=min(r.rmn,r.sum+l.rmn);
ans.rmx=max(r.rmx,r.sum+l.rmx);
ans.L=max(0,l.L-r.R)+r.L;
ans.R=max(0,r.R-l.L)+l.R;
return ans;
}
void ask(int x,int y) {
Tree X,Y;
clear(X); clear(Y);
int f1=top[x],f2=top[y];
while (f1!=f2) {
if (deep[f1]>deep[f2]) {
X=update(X,ask_T(1,1,n,num[f1],num[x]));
x=pre[f1]; f1=top[x];
} else {
Y=update(Y,ask_T(1,1,n,num[f2],num[y]));
y=pre[f2]; f2=top[y];
}
}
if (num[x]>num[y]) X=update(X,ask_T(1,1,n,num[y],num[x]));
else Y=update(Y,ask_T(1,1,n,num[x],num[y]));
int maxx=0;
if (X.L==Y.R&&X.R==0&&Y.L==0)
maxx=max(maxx,max(X.lmx,X.sum+Y.lmx));
if (X.R==Y.L&&X.L==0&&Y.R==0)
maxx=max(maxx,max(Y.lmx,Y.sum+X.lmx));
ANS=max(ANS,maxx);
}
想清楚就好了
虽然链剖完之后,路径上的两条链可能方向不一样
但是不影响未匹配的左括号和右括号个数(举例画图就可以理解了)
100%
题解写的太简单了:
不是很明白,只能从代码中慢慢琢磨一下
找重心还是很naive的
void solve(int now) {
cal(now);
vis[now]=1;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
{
sz=size[way[i].y];
root=0;
findroot(way[i].y,now);
solve(root);
}
}
void findroot(int now,int fa) {
size[now]=1;
f[now]=0;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&!vis[way[i].y]) {
findroot(way[i].y,now);
size[now]+=size[way[i].y];
f[now]=max(f[now],size[way[i].y]);
}
f[now]=max(f[now],sz-size[now]);
if (f[now]<f[root]) root=now;
}
重点就在于cal:
处理root的所有子结点
之后我惊奇的发现,这样处理可以使得到答案的左右两条链一定在两个不同的子树中,不用去重了
dfs_1就是专门计算答案的:当前子树和之前遍历过的子树之间形成的答案
dfs_2:把当前子树的信息统计一下
void cal(int now) {
top=0;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
S[++top]=way[i].y;
for (int i=1;i<=top;i++) {
dfs_1(S[i],root,0,0,0);
dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
}
clear(root,0,0,0,0);
for (int i=top;i>=1;i--) {
dfs_1(S[i],root,0,0,0);
dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
}
clear(root,0,0,0,0);
}
dfs_1
lmx
l
m
x
:最大前缀和(嵌套层数)
rmx
r
m
x
:左括号数
sum
s
u
m
:区间和(左括号数量和右括号数量的差值)
如果
rmx<=0
r
m
x
<=
0
,说明肯定有多余的右括号,那么当前的嵌套层数应该是右括号的前缀和:
lmx−sum(sum<0)
l
m
x
−
s
u
m
(
s
u
m
<
0
)
(sum:右括号比左括号多的数量)
如果 sum==−1 s u m == − 1 ,而当前结点上的恰是左括号,说明当前就能形成一个合法的括号序列,维护一下答案
然而真正的难点在于mp的含义:
mp[sum][0]
m
p
[
s
u
m
]
[
0
]
:区间和是sum这种状态是否到达过
mp[sum][1]
m
p
[
s
u
m
]
[
1
]
:区间和是sum,最大的嵌套层数
且mp中的sum只针对左括号较多的情况
void dfs_1(int now,int fa,int lmx,int rmx,int sum) {
lmx=max(sum+val[now],lmx);
rmx=max(rmx+val[now],0);
sum+=val[now];
if (rmx<=0) {
if (mp[-sum][0]&&ans<max(mp[-sum][1],lmx-sum))
ans=max(mp[-sum][1],lmx-sum);
if (sum==-1&&val[root]==1&&ans<max(1,lmx+1))
ans=max(1,lmx+1);
}
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
dfs_1(way[i].y,now,lmx,rmx,sum);
}
dfs_2
lmx
l
m
x
:左括号数量
lmn
l
m
n
:右括号数量(负数)
lmn>=0 l m n >= 0 (实际上就是 lmn==0 l m n == 0 ),说明没有右括号
void dfs_2(int now,int fa,int lmx,int lmn,int sum) {
lmx=max(lmx+val[now],0);
lmn=min(lmn+val[now],0);
sum+=val[now];
if (lmn>=0) {
mp[sum][0]=1;
if (mp[sum][1]<lmx)
mp[sum][1]=lmx;
if (sum==0&&ans<mp[sum][1])
ans=mp[sum][1];
}
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
dfs_2(way[i].y,now,lmx,lmn,sum);
}
clear
lmx
l
m
x
:左括号数量
lmn
l
m
n
:右括号数量
void clear(int now,int fa,int lmx,int lmn,int sum) {
lmx=max(lmx+val[now],0);
lmn=min(lmn+val[now],0);
sum+=val[now];
if (lmn>=0)
mp[sum][0]=mp[sum][1]=0;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
clear(way[i].y,now,lmx,lmn,sum);
}
tip
在后来回味的时候,突然发现这种统计答案的方法类似很久之前zyh学长讲的某种“树上玄学二分”
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
struct node{
int y,nxt;
};
node way[N<<1];
int n,sz,root,st[N],size[N],f[N],tot=0;
int ans=0,val[N],mp[N][2];
bool vis[N];
void add(int u,int w) {
tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
tot++;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}
void findroot(int now,int fa) {
size[now]=1;
f[now]=0;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&!vis[way[i].y]) {
findroot(way[i].y,now);
size[now]+=size[way[i].y];
f[now]=max(f[now],size[way[i].y]);
}
f[now]=max(f[now],sz-size[now]);
if (f[now]<f[root]) root=now;
}
int S[N],top;
void dfs_1(int now,int fa,int lmx,int rmx,int sum) {
lmx=max(sum+val[now],lmx);
rmx=max(rmx+val[now],0);
sum+=val[now];
if (rmx<=0) {
if (mp[-sum][0]&&ans<max(mp[-sum][1],lmx-sum))
ans=max(mp[-sum][1],lmx-sum);
if (sum==-1&&val[root]==1&&ans<max(1,lmx+1))
ans=max(1,lmx+1);
}
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
dfs_1(way[i].y,now,lmx,rmx,sum);
}
void dfs_2(int now,int fa,int lmx,int lmn,int sum) {
lmx=max(lmx+val[now],0);
lmn=min(lmn+val[now],0);
sum+=val[now];
if (lmn>=0) {
mp[sum][0]=1;
if (mp[sum][1]<lmx)
mp[sum][1]=lmx;
if (sum==0&&ans<mp[sum][1])
ans=mp[sum][1];
}
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
dfs_2(way[i].y,now,lmx,lmn,sum);
}
void clear(int now,int fa,int lmx,int lmn,int sum) {
lmx=max(lmx+val[now],0);
lmn=min(lmn+val[now],0);
sum+=val[now];
if (lmn>=0)
mp[sum][0]=mp[sum][1]=0;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y]&&way[i].y!=fa)
clear(way[i].y,now,lmx,lmn,sum);
}
void cal(int now) {
top=0;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
S[++top]=way[i].y;
for (int i=1;i<=top;i++) {
dfs_1(S[i],root,0,0,0);
dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
}
clear(root,0,0,0,0);
for (int i=top;i>=1;i--) {
dfs_1(S[i],root,0,0,0);
dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
}
clear(root,0,0,0,0);
}
void solve(int now) {
cal(now);
vis[now]=1;
for (int i=st[now];i;i=way[i].nxt)
if (!vis[way[i].y])
{
sz=size[way[i].y];
root=0;
findroot(way[i].y,now);
solve(root);
}
}
int main()
{
scanf("%d",&n);
for (int i=2;i<=n;i++) {
int x;
scanf("%d",&x);
add(x,i);
}
char s[3];
for (int i=1;i<=n;i++) {
scanf("%s",s);
if (s[0]=='(') val[i]=1;
else val[i]=-1;
}
sz=n; root=0; f[0]=n;
findroot(1,0);
solve(root);
printf("%d",ans);
return 0;
}