洛谷 P5658 [CSP-S2019] 括号树

链接:

P5658


分析:

显然我们应该在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!

posted @ 2021-08-05 11:31  llmmkk  阅读(215)  评论(0)    收藏  举报