视频加载失败

安吉D11-B

1431 字
7 分钟
安吉D11-B
原题呈现

P5658 括号树#

题目背景#

本题中合法括号串的定义如下:

  1. () 是合法括号串.
  2. 如果 A 是合法括号串,则 (A) 是合法括号串.
  3. 如果 AB 是合法括号串,则 AB 是合法括号串.

本题中子串不同的子串的定义如下:

  1. 字符串 S 的子串是 S连续的任意个字符组成的字符串.S 的子串可用起始位置 ll 与终止位置 rr 来表示,记为 S(l,r)S (l, r)1lrS1 \leq l \leq r \leq |S |S|S | 表示 S 的长度).
  2. S 的两个子串视作不同当且仅当它们在 S 中的位置不同,即 ll 不同或 rr 不同.

题目描述#

一个大小为 nn 的树包含 nn 个结点和 n1n - 1 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达.

小 Q 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 nn 的树,树上结点从 1n1 \sim n 编号,11 号结点为树的根.除 11 号结点外,每个结点有一个父亲结点,uu2un2 \leq u \leq n)号结点的父亲为 fuf_u1fu<u1 ≤ f_u < u)号结点.

小 Q 发现这个树的每个结点上恰有一个括号,可能是 ().小 Q 定义 sis_i 为:将根结点到 ii 号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串.

显然 sis_i 是个括号串,但不一定是合法括号串,因此现在小 Q 想对所有的 ii1in1\leq i\leq n)求出,sis_i 中有多少个互不相同的子串合法括号串

这个问题难倒了小 Q,他只好向你求助.设 sis_i 共有 kik_i 个不同子串是合法括号串, 你只需要告诉小 Q 所有 i×kii \times k_i 的异或和,即:

(1×k1) xor (2×k2) xor (3×k3) xor  xor (n×kn) (1 \times k_1)\ \text{xor}\ (2 \times k_2)\ \text{xor}\ (3 \times k_3)\ \text{xor}\ \cdots\ \text{xor}\ (n \times k_n)

其中 xor\text{xor} 是位异或运算.

输入格式#

第一行一个整数 nn,表示树的大小.

第二行一个长为 nn 的由 () 组成的括号串,第 ii 个括号表示 ii 号结点上的括号.

第三行包含 n1n − 1 个整数,第 ii1i<n1 \leq i \lt n)个整数表示 i+1i + 1 号结点的父亲编号 fi+1f_{i+1}

输出格式#

仅一行一个整数表示答案.

输入输出样例 #1#

输入 #1#

5
(()()
1 1 2 2

输出 #1#

6

说明/提示#

【样例 1 解释】

树的形态如下图:

将根到 1 号结点的简单路径上的括号,按经过顺序排列所组成的字符串为 (,子串是合法括号串的个数为 00

将根到 2 号结点的字符串为 ((,子串是合法括号串的个数为 00

将根到 3 号结点的字符串为 (),子串是合法括号串的个数为 11

将根到 4 号结点的字符串为 (((,子串是合法括号串的个数为 00

将根到 5 号结点的字符串为 ((),子串是合法括号串的个数为 11

【样例 2】

见选手目录下的 brackets/brackets2.in 与 brackets/brackets2.ans.

【数据范围】

测试点编号nn\le特殊性质
121\sim 288fi=i1f_i=i-1
343\sim 4200200^
575\sim 720002000^
8108\sim 10^
111411\sim 1410510^5fi=i1f_i=i-1
151615\sim 16^
172017\sim 205×1055\times 10^5^

既然是一棵树,不知道怎么做的时候就搜索.尝试 dfs,并在搜索时维护一些数据.

显然,我们在这道题中发现了括号,括号和栈是紧密相关的,可以在搜索时维护一个栈,栈内存储当前路径上还没有被匹配的左括号的下标.

最后,我们还发现了子串,看到了子串或子序列,就要想到 dp,并定义状态为:

定义 dp[i] 为以……结尾……的方案数(长度)

因此,此题我们定义 dp[i] 表示以节点 ii 结尾的方案数.

这道题已经拆解完了,接下来就只需要进行一次搜索,并在搜索时动态维护栈、dp 数组、题目中的 k 数组即可.具体来说,当我们遍历到节点 uu 时:

  • 将初始化 dpu=0,ku=kfaudp_u=0,k_u=k_{fa_u}faufa_uuu 的父节点.
  • 检查当前节点的括号
    • 若是左括号,则将 uu 入栈.由于不可能有合法的括号序列是以左括号为结尾的,因此 dpudp_u 仍为 0.
    • 若是右括号,检查栈中有没有可以配对的左括号
      • 若是没有,则这个右括号不能成功匹配,因此 dpudp_u 仍为 0.
      • 若是存在,则完成一次匹配,将栈顶的左括号出栈.设这个左括号的位置为 xx,则 dpfaxdp_{fa_x} 为在这对括号配成之前就组合成的子串,且恰好以 faxfa_x 结尾.显然,这每一个子串都可以加上 [x,u][x,u],成为一个新的子串.因此,dpu=dpfax+1dp_u = dp_{fa_x} + 1kuk_u 增加 dpudp_u
  • 继续向下 dfs.
  • 将栈还原.

依照这个流程,可以写出标程

#include <bits/stdc++.h>
using namespace std;
#define int long long
const 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;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D11-B
https://blog.jerrylab.top/posts/problem/anji2026/D11-B/
作者
Jerry
发布于
2026-08-12
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0