安吉D11-B
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B本文
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
P5658 括号树
题目背景
本题中合法括号串的定义如下:
()是合法括号串.- 如果
A是合法括号串,则(A)是合法括号串. - 如果
A,B是合法括号串,则AB是合法括号串.
本题中子串与不同的子串的定义如下:
- 字符串
S的子串是S中连续的任意个字符组成的字符串.S的子串可用起始位置 与终止位置 来表示,记为 (, 表示 S 的长度). S的两个子串视作不同当且仅当它们在S中的位置不同,即 不同或 不同.
题目描述
一个大小为 的树包含 个结点和 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达.
小 Q 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 的树,树上结点从 编号, 号结点为树的根.除 号结点外,每个结点有一个父亲结点,()号结点的父亲为 ()号结点.
小 Q 发现这个树的每个结点上恰有一个括号,可能是 ( 或 ).小 Q 定义 为:将根结点到 号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串.
显然 是个括号串,但不一定是合法括号串,因此现在小 Q 想对所有的 ()求出, 中有多少个互不相同的子串是合法括号串.
这个问题难倒了小 Q,他只好向你求助.设 共有 个不同子串是合法括号串, 你只需要告诉小 Q 所有 的异或和,即:
其中 是位异或运算.
输入格式
第一行一个整数 ,表示树的大小.
第二行一个长为 的由 ( 与 ) 组成的括号串,第 个括号表示 号结点上的括号.
第三行包含 个整数,第 ()个整数表示 号结点的父亲编号 .
输出格式
仅一行一个整数表示答案.
输入输出样例 #1
输入 #1
5(()()1 1 2 2输出 #1
6说明/提示
【样例 1 解释】
树的形态如下图:

将根到 1 号结点的简单路径上的括号,按经过顺序排列所组成的字符串为 (,子串是合法括号串的个数为 .
将根到 2 号结点的字符串为 ((,子串是合法括号串的个数为 .
将根到 3 号结点的字符串为 (),子串是合法括号串的个数为 .
将根到 4 号结点的字符串为 (((,子串是合法括号串的个数为 .
将根到 5 号结点的字符串为 ((),子串是合法括号串的个数为 .
【样例 2】
见选手目录下的 brackets/brackets2.in 与 brackets/brackets2.ans.
【数据范围】
| 测试点编号 | 特殊性质 | |
|---|---|---|
| ^ | ||
| ^ | ||
| ^ | 无 | |
| ^ | 无 | |
| ^ |
既然是一棵树,不知道怎么做的时候就搜索.尝试 dfs,并在搜索时维护一些数据.
显然,我们在这道题中发现了括号,括号和栈是紧密相关的,可以在搜索时维护一个栈,栈内存储当前路径上还没有被匹配的左括号的下标.
最后,我们还发现了子串,看到了子串或子序列,就要想到 dp,并定义状态为:
定义
dp[i]为以……结尾……的方案数(长度)
因此,此题我们定义 dp[i] 表示以节点 结尾的方案数.
这道题已经拆解完了,接下来就只需要进行一次搜索,并在搜索时动态维护栈、dp 数组、题目中的 k 数组即可.具体来说,当我们遍历到节点 时:
- 将初始化 , 是 的父节点.
- 检查当前节点的括号
- 若是左括号,则将 入栈.由于不可能有合法的括号序列是以左括号为结尾的,因此 仍为 0.
- 若是右括号,检查栈中有没有可以配对的左括号
- 若是没有,则这个右括号不能成功匹配,因此 仍为 0.
- 若是存在,则完成一次匹配,将栈顶的左括号出栈.设这个左括号的位置为 ,则 为在这对括号配成之前就组合成的子串,且恰好以 结尾.显然,这每一个子串都可以加上 ,成为一个新的子串.因此,, 增加 .
- 继续向下 dfs.
- 将栈还原.
依照这个流程,可以写出标程:
#include <bits/stdc++.h>using namespace std;#define int long longconst int N = 5e5 + 10;int n, f[N], ans, dp[N], k[N];stack<int> sta;vector<int> e[N];char c[N];
void dfs(int t) { k[t] = k[f[t]]; int top = -1; if (c[t] == '(') { sta.push(t); } else { if (sta.empty()) { // 无法匹配 dp[t] = 0; } else { // 可以匹配 top = sta.top(); sta.pop(); dp[t] = dp[f[top]] + 1; k[t] += dp[t]; } } for (int i = 0; i < e[t].size(); i++) { int v = e[t][i]; dfs(v); } if (c[t] == '(') { sta.pop(); } else if (top != -1) { // 可以匹配 sta.push(top); }}
signed main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> c[i]; } for (int i = 2; i <= n; i++) { cin >> f[i]; e[f[i]].push_back(i); } dfs(1); for (int i = 1; i <= n; i++) { ans ^= i * k[i]; } cout << ans; return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


