CSP-S2 2019 D1T2 括號樹題解


說在前面的話

謹以此篇題解,紀念我初中的兩年\(OI\)生涯以及不長不短的停課時光。

但願高中還能夠繼續學習\(OI\)吧,也衷心希望其他\(OIer\)不要再犯類似我的錯誤。

題意

原題鏈接

給定一棵有\(n\)個節點的樹,每個節點對應一個括號(左或右),需要求出從該節點到根節點的簡單路徑所構成的括號串中合法括號子串的數量。
其中合法括號串的定義是這樣的

  1. ()是合法括號串。
  2. 如果A是合法括號串,則(A)是合法括號串。
  3. 如果AB是合法括號串,則AB是合法括號串。

其中\(n\le 5\times 10^5\)

題解

1. 初步思路

\(cnt_i\)表示從\(i\)節點到根節點所構成的括號串中合法括號子串的數量,\(f\)表示\(i\)節點的父親,則有一個很顯然的結論:

\[cnt_i=cnt_f+(\text{以i節點為結尾的合法括號子串的個數}) \]

這個結論應該無需證明了……這是解題的關鍵,於是我們就只需要考慮以\(i\)節點為結尾的合法括號子串的個數了(設其為\(t_i\))。

2. 統計答案

先考慮怎么暴力統計,顯然直接從\(i\)節點往上跳,統計合法括號子串數即可,這樣做是\(O(n_2)\)的。

再考慮給出的合法括號串的定義:“如果AB是合法括號串,則AB是合法括號串”

舉個例子,考慮暴力的過程,如果以\(i\)為結尾的括號串是()()()(),我們總共了統計了\(4\)個,但其實,我們只需要統計最右邊(dfs序最大)的那一對合法的括號,而左邊的三對括號就相當於\(t_{f_f}\)(即以\(f_f\)為結尾的合法括號串的數量),這是因為最左邊的括號串()合法,右邊的三個括號串()()()()()()合法,聯系上面的性質,所以將它們連起來同樣合法。

到這一步之后,貌似大多數人都用棧來做,我在考場上\(yy\)出了一種奇♂妙的方法,在這里分享一下。

於是我們設\(re_i\)為滿足\(i\)節點到該節點所構成的括號串為合法括號串,且深度最大的節點,得到\(cnt_i\)的表達式

\[cnt_i=cnt_f+t_{f_{re_i}}+1 \]

結合一下先前的例子()()()()\(cnt_f\)不用多講,\(1\)表示的是以\(re_i\)為開頭以\(i\)為結尾的合法括號串(即例子中最左邊的的()),\(t_{f_{re_i}}\)就是例子中右邊的三個合法括號串()()()()()(),至於這三個括號串為什么要記入答案上文已討論。

再結合一下代碼看看:

cnt[x]+=cnt[f],fa[x]=f;
if (a[x]==1) // 只有a[x]==1(即括號為')')才有可能存在以x為結尾的合法括號串
{
	while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]]; // 找到re[x]
	if (f==0||a[f]==1) re[x]=-1; // re[x]需合法
	else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1; // 統計答案。cnt[fa[f]]-cnt[fa[fa[f]]]就等於上文中的t[fa[f]]
}
else re[x]=-1;

經驗教訓

筆者在考場做這道題時,將上文中的while打成了if,於是(洛谷自測)\(100 \to 10\)

其實是沒有考慮到這種情況((())())。(至於為什么錯可以手玩一下)

然后就開開心心\(\text{AFO}\)搞文化課了

這里以親身教訓提醒大家,一定要注意細節!考慮情況一定要充分!不要重蹈我的覆轍!

祝大家人人取得滿意的成績(我是拿不到了)

代碼

其實代碼就很短啦,\(qwq\)

#include <stdio.h>

using namespace std;

template <typename T> inline void Read(T &t)
{
	int c=getchar(),f=0;
	for (;c<'0'||c>'9';c=getchar()) f=(c=='-');
	for (t=0;c>='0'&&c<='9';c=getchar()) t=(t<<3)+(t<<1)+(c^48);
	if (f) t=-t;
}

typedef long long ll;
const int N=5e5+5;

int n,tot,head[N],a[N],fa[N],re[N];
ll ans,cnt[N];
char temp[N];
 
struct Edge
{
	int to,next;
	void add(int x, int y) { to=y,next=head[x],head[x]=tot; }
} e[N<<1];
 
void dfs(int x, int f)
{
	cnt[x]+=cnt[f],fa[x]=f;
	if (a[x]==1)
	{
		while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]];
		if (f==0||a[f]==1) re[x]=-1;
		else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1;
	}
	else re[x]=-1;
	for (int i=head[x];i;i=e[i].next)
	{
		int v=e[i].to;
		dfs(v,x);
	}
}	

signed main()
{
	Read(n);
	scanf("%s",temp);
	for (int i=1;i<=n;i++) 
		a[i]=(temp[i-1]=='('?-1:1);
	for (int i=2,f;i<=n;i++) Read(f),e[++tot].add(f,i);

	re[0]=-1;
	dfs(1,0);
	
	for (ll i=1;i<=n;i++) ans^=(cnt[i]*i);
	printf("%lld\n",ans);
	
    return 0;
}

結語

筆者大概率是\(\text{AFO}\)了,但還是希望這篇題解能給做出或沒有做出這道題的人帶來一些幫助,也算是我\(OI\)生涯的回光返照

去搞文化課准備中考了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM