- 題庫 :洛谷
- 題號 :5658
- 題目 :括號樹
- link :https://www.luogu.com.cn/problem/P5658
55分鏈做法 :這道題鏈的做法能給很多分,我們設一個右括號的貢獻值為當前序列加上這個右括號后可以多獲得的匹配數(左括號沒有貢獻值)。
觀察序列1:
()()()
i = 2的時候,對答案的貢獻值為1 。
i = 4的時候,本身[3, 4]就有一個滿足要求的括號序列,再合並上前面的成為[1, 4],於是對答案的貢獻值就為2,再加上前面[1, 2]本身有的括號序列,總共為3。
i = 6時,總共的貢獻值為3,加上前面的有3 + 3 = 6種。其他位置均沒有貢獻(左括號沒有貢獻值)。
總之,i為1 — 6時對答案的貢獻分別為0, 1, 0, 2, 0, 3合並后的總答案為0, 1, 1, 3, 3, 6。
觀察序列2:
())()
i = 2時,對答案貢獻為1。
i = 3時,由於不滿足成匹配的括號序列,所以沒有貢獻(我們只看右括號的貢獻值)。
i = 5時,由於i = 3時多了一個后括號,所以[1, 3]不匹配,導致[1, 5]成不了一個匹配的括號序列,所以對答案的貢獻仍為1
總之,i為1 — 5時對答案的貢獻分別為0, 1, 0, 0, 1合並后的總答案為0, 1, 1, 1, 2。
觀察序列3:
()(())
i = 2時,貢獻為1。
i = 5時,由於i = 3是在中間斷開,所以[1, 5]不能匹配,所以貢獻仍為1。
i = 6時,我們發現[1, 2]是匹配的。故[1, 2], [3, 6]能合成一個匹配的序列,所以對答案貢獻為2。
總之,i為1 — 6時對答案的貢獻分別為0, 1, 0, 0, 1, 2,合並后的總答案為0, 1, 1, 1, 2, 4
我們發現,一個后括號如果能匹配一個前括號,假設這個前括號的前1位同樣有一個已經匹配了的后括號,那么我們就可以把當前的匹配括號序列和之前的匹配括號序列合並。當前的這個后括號的貢獻值,其實就等於前面那個后括號的貢獻值 + 1。
code:
1 #include <bits/stdc++.h> 2 #define INF 0x3f3f3f3f 3 #define int unsigned long long//數據原因 4 using namespace std; 5 int n, q[1000001], sum[1000001], tot[1000001], ans;//tot是貢獻值,sum是貢獻值總和 6 char s[1000001]; 7 stack < int > pru;//棧存左括號的位置 8 signed main() 9 { 10 scanf("%llu", &n); 11 scanf("%s", s + 1); 12 for(register int i = 1; i <= n; ++i) 13 { 14 scanf("%llu", &q[i]); 15 } 16 for(register int i = 1; i <= n; ++i)//遍歷鏈序列(都是連續的) 17 { 18 if(s[i] == '(')//左括號 19 { 20 pru.push(i);//push進棧中,用來存第一個左括號的位置 21 } 22 else//右括號 23 { 24 if(pru.size() != 0)//判斷棧非空 25 { 26 int l = pru.top();//取出最近左括號的位置(沒用過的) 27 tot[i] = tot[l - 1] + 1;//上一個后括號的貢獻值 + 1 28 pru.pop();//彈出棧 29 } 30 } 31 sum[i] = sum[i - 1] + tot[i];//sum記錄貢獻值總和 32 } 33 ans = sum[1]; 34 for(register int i = 2; i <= n; ++i)//異或 35 { 36 ans ^= i * sum[i]; 37 } 38 printf("%llu", ans); 39 return 0; 40 }
滿分做法 :其實很簡單,只需要把在鏈上完成的操作轉換到樹上來完成就行了,但是要注意回溯,而且之前的最近的左括號的上一個括號的貢獻值變為最近的左括號的父親的貢獻值,因為樹上一個點的上一個值就是這個點的父親。
code:
1 #include <bits/stdc++.h> 2 #define INF 0x3f3f3f3f 3 #define int unsigned long long//此題數據很大 4 using namespace std; 5 int n, sum[500001], tot[500001], ans, head[500001], num, fa[500001]; 6 char s[500001]; 7 stack < int > pru; 8 struct node//鏈式前向星 9 { 10 int next, to; 11 }stu[1000001]; 12 inline void add(int x, int y)//加邊 13 { 14 stu[++num].next = head[x]; 15 stu[num].to = y; 16 head[x] = num; 17 return; 18 } 19 inline void dfs(int u, int father)//樹上的鏈 20 { 21 int l = -1;//回溯的需要 22 if(s[u] == '(') 23 { 24 l = 0;//表示回溯需要pop 25 pru.push(u); 26 } 27 else 28 { 29 if(pru.size() != 0) 30 { 31 l = pru.top(); 32 tot[u] = tot[fa[l]] + 1;//最近左括號父節點貢獻值 + 1 33 pru.pop(); 34 } 35 } 36 sum[u] = sum[fa[u]] + tot[u];//計算總貢獻值 37 for(register int i = head[u]; i; i = stu[i].next)//搜索 38 { 39 int k = stu[i].to; 40 if(k != father)//判節點是否可以搜 41 { 42 dfs(k, u); 43 } 44 } 45 if(l == 0)//回溯 46 { 47 pru.pop(); 48 } 49 else if(l != -1)//如果 = -1就代表什么都沒push或pop 50 { 51 pru.push(l); 52 } 53 return; 54 } 55 signed main() 56 { 57 scanf("%llu", &n); 58 scanf("%s", s + 1); 59 for(register int i = 2; i <= n; ++i) 60 { 61 scanf("%llu", &fa[i]); 62 add(i, fa[i]);//加邊 63 add(fa[i], i);//加反向邊 64 } 65 dfs(1, -1);//搜索 66 ans = sum[1]; 67 for(register int i = 2; i <= n; ++i)//異或 68 { 69 ans ^= i * sum[i]; 70 } 71 printf("%llu", ans); 72 return 0; 73 }