洛谷 P5658 [CSP-S2019] 括号树
链接:
分析:
显然我们应该在dfs树的同时维护每个点的答案。
注意到第 \(u\) 个点的答案可以分成两部分,不包含 \(u\) 点时的答案,和加入 \(u\) 点后新增的答案,前者可以从父节点继承下来,所以我们对于每个点考虑的是加入该点后新增的答案。
在dfs树时会回溯,所以我们还需要考虑撤销这个点带来的影响。
所以我们实际要做的就是思考出一种策略,使其能够对新加入的点维护出新增的答案,还能将这个点的影响撤销。
算法:
对于一个点,我们分类讨论左括号和右括号的两种操作。
对于加入点的策略。
首先让当前点继承父节点的答案,然后考虑新增答案。
如果加入的是右括号,它可能会与前面的第一个左括号匹配,所以考虑对左括号维护一个栈。考虑这两个括号中间的部分,它一定是一个合法子串或空串(假如中间有不匹配的右括号,那么它一定会和当前左括号匹配,假如中间有不匹配的左括号,那么它会成为第一个左括号,变成当前右括号的匹配对象)。所以当前右括号匹配后一定会新增一个答案,考虑其他的答案,一定是多组匹配的括号相连,形如\((\cdots)(\cdots)(\cdots)\)。所以我们用while循环查询当前now节点匹配的左括号的前一个是否是右括号且有匹配(匹配括号内部一样也是合法子串。同时我们需要对右括号维护一个匹配左括号):如果是,那么答案++,并将now更新为这个右括号,循环查询;如果不是,那么退出。这样就可以正确地维护出当前加入点的新增答案了。
如果加入的是左括号,那么不会有新增的答案(不可能与另一个右括号匹配),只需维护栈。
对于退出点时策略。
如果退出的是右括号,那么其他点的影响只有“可能占据了一个匹配的左括号“,重新在栈里加入这个左括号即可。
如果退出的是左括号,那么在栈里弹出即可。
优化:
基于以上算法,我们可以写出这样的程序:
void dfs(int u){
dp[u]=dp[fa[u]];
if(c[u]=='(') sta[++top]=u;
else{
if(top){
dp[u]++;
match[u]=sta[top--];
int now=u;
while(match[fa[match[now]]]){
now=fa[match[now]];
dp[u]++;
}
}
}
ans^=(u*dp[u]);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
if(c[u]=='(')top--;
else if(match[u])sta[++top]=match[u];
}
但是只获得了8AC,2TLE的好成绩。
反思整个算法,最可能影响时间复杂度的就是那个while循环了。我们看循环的过程,它实际是在不停向上跳,返回右括号的数量,对于一个右括号向上跳的结果是不会被后面的节点影响的,所以我们可以对每个右括号维护一个up值,对于一个右括号,只用在上一个右括号的up值加1就是当前的up值。新增的答案就是当前的up值了。
这样就把一个while循环优化成了 \(O(1)\) 查询。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int N=5e5+5;
int n;
char c[N];
struct edge{
int v,next;
}e[N];
int head[N],en;
void insert(int u,int v){
e[++en].v=v;
e[en].next=head[u];
head[u]=en;
}
int fa[N];
int sta[N],top;//left(
int match[N],up[N];//right)
int ans;
int dp[N];
void dfs(int u){
dp[u]=dp[fa[u]];
if(c[u]=='(') sta[++top]=u;
else if(top){
match[u]=sta[top--];
up[u]=up[fa[match[now]]]+1;
dp[u]+=up[u];
}
ans^=(u*dp[u]);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
if(c[u]=='(')top--;
else if(match[u])sta[++top]=match[u];
}
signed main(){
n=in;
for(int i=1;i<=n;i++)
cin>>c[i];
for(int i=2;i<=n;i++){
fa[i]=in;
insert(fa[i],i);
}
dfs(1);
cout<<ans;
return 0;
}
题外话:
两年前的普及组蒟蒻看到这题就放弃了场外提高,两年后的我看到这题在30分钟内成功AC!

浙公网安备 33010602011771号