1. 程式人生 > 其它 >洛谷 P5658 [CSP-S2019] 括號樹

洛谷 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!