洛谷P5658 括号树(CSP-S 2019 D1T2)题解 动态规划
题目链接:https://www.luogu.com.cn/problem/P5658
解题思路:
我们不妨将这道题进行一下简化,变成如下题目描述:
给你一个字符串,求这个字符串中有多少合法括号子串,看看能不能在O(n)时间复杂度内解决这个问题。
对于这样一个问题,我们可以用栈存储所有没有匹配上的 ‘(’ 对应的坐标,然后用数组 match[i]
表示当前位置 i
对应的刚好匹配上的左括号的坐标(当然前提是栈中有元素且当前元素 s[i] == ')'
)。
然后我们用 sum[i]
表示以 s[i]
结尾的合法括号子串的数量,则:如果 match[i] != 0
,sum[i] = sum[match[i]-1] + 1
。
答案就是 \(\sum{sum[i]}\) 。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000010;
stack<int> stk;
int match[maxn];
char s[maxn];
long long sum[maxn], ans;
int main() {
scanf("%s", s+1);
int n = strlen(s+1);
for (int i = 1; i <= n; i ++) {
if (s[i] == '(') stk.push(i);
else {
if (!stk.empty()) {
int p = stk.top();
stk.pop();
sum[i] = sum[p-1] + 1;
}
}
}
for (int i = 1; i <= n; i ++) ans += sum[i];
printf("%lld\n", ans);
return 0;
}
同样的思路,放到树上,只要在dfs的过程中进行同样的处理即可。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000010;
int n, match[maxn], stk[maxn], sp[maxn], f[maxn], sz;
long long sum[maxn], tot[maxn], ans;
char s[maxn];
vector<int> g[maxn];
void dfs(int u, int id) {
if (s[u] == '(') {
sp[u] = id;
id = u;
}
else {
if (id) {
match[u] = id;
sum[u] = 1 + sum[f[match[u]]];
id = sp[id];
}
}
tot[u] = tot[f[u]] + sum[u];
int sz = g[u].size();
for (int i = 0; i < sz; i ++) {
int v = g[u][i];
dfs(v, id);
}
}
int main() {
scanf("%d%s", &n, s+1);
for (int i = 2; i <= n; i ++) {
scanf("%d", f+i);
g[ f[i] ].push_back(i);
}
dfs(1, 0);
for (int i = 1; i <= n; i ++)
ans ^= (long long) i * tot[i];
printf("%lld\n", ans);
return 0;
}
当然这里处理的时候要注意一些细节,其中最不方便思考的一个问题是:栈如何保存。
所以这里我单独开了一个 \(sp[i]\) 用于表示在 \(i\) 刚添加进栈的时刻之前栈顶元素的坐标。
\(sum[i]\) 表示的还是以 \(s[i]\) 结尾的合法括号串数量。
\(tot[i]\) 表示的是根节点到 \(i\) 号节点的所有点的 \(sum\) 值之和。
最终的答案就是所有的 \(i \times tot[i]\) 的异或和。
当然这里还需要注意一点就是其实我的 \(match[]\) 数组可以不开,但是开着方便理解,所以我就没有去掉了。