2019CSP day1t2 括号树
题目背景
本题中合法括号串的定义如下:
()是合法括号串。- 如果
A是合法括号串,则(A)是合法括号串。 - 如果
A,B是合法括号串,则AB是合法括号串。
本题中子串与不同的子串的定义如下:
- 字符串
S的子串是S中连续的任意个字符组成的字符串。S的子串可用起始位置 \(l\) 与终止位置 \(r\) 来表示,记为 S (l, r)(\(1 \leq l \leq r \leq |S |\)\(|S |\) 表示 \(S\) 的长度)。 S的两个子串视作不同当且仅当它们在S中的位置不同,即 \(l\) 不同或 \(r\) 不同。
题目描述
一个大小为 \(n\) 的树包含 \(n\) 个结点和 \(n − 1\) 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达。
小 \(Q\) 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 \(n\) 的树,树上结点从 \(1\) ∼ \(n\) 编号,\(1\) 号结点为树的根。除 \(1\) 号结点外,每个结点有一个父亲结点,\(u\)(\(2 \leq u \leq n\))号结点的父亲为 \(f_u\)(\(1 ≤ f_u < u\))号结点。
小 \(Q\) 发现这个树的每个结点上恰有一个括号,可能是( 或)。小 \(Q\) 定义 \(s_i\) 为:将根结点到 \(i\) 号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串。
显然 \(s_i\) 是个括号串,但不一定是合法括号串,因此现在小 \(Q\) 想对所有的 \(i\)(\(1\leq i\leq n\))求出,\(s_i\) 中有多少个互不相同的子串是合法括号串。
这个问题难倒了小 \(Q\),他只好向你求助。设 \(s_i\) 共有 \(k_i\) 个不同子串是合法括号串, 你只需要告诉小 Q 所有 \(i \times k_i\) 的异或和,即:
\((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\) 是位异或运算。
输入格式
第一行一个整数 \(n\),表示树的大小。
第二行一个长为 \(n\) 的由( 与) 组成的括号串,第 \(i\) 个括号表示 \(i\) 号结点上的括号。
第三行包含 \(n − 1\) 个整数,第 \(i\)(\(1 \leq i \lt n\))个整数表示 \(i + 1\) 号结点的父亲编号 \(f_{i+1}\)。
输出格式
仅一行一个整数表示答案。
输入输出样例
输入 #1
5
(()()
1 1 2 2
输出 #1
6
说明/提示
【样例解释1】
树的形态如下图:

将根到 \(1\) 号结点的简单路径上的括号,按经过顺序排列所组成的字符串为 (,子串是合法括号串的个数为 \(0\)。
将根到 \(2\) 号结点的字符串为 ((,子串是合法括号串的个数为 \(0\)。
将根到 \(3\) 号结点的字符串为 (),子串是合法括号串的个数为 \(1\)。
将根到 \(4\) 号结点的字符串为 (((,子串是合法括号串的个数为 \(0\)。
将根到 \(5\) 号结点的字符串为 ((),子串是合法括号串的个数为 \(1\)。
【数据范围】

明显的树形\(DP\)。
思路有点难讲。花了考场上\(1.5h\)想出来的。
先考虑暴力。我们需要一个能过\(50pts\)的暴力,所以对于每一个节点,我们必须在最多\(O(n)\)的时间处理出答案贡献。
考虑类似单调性优化的方法。容易想到,对于一个点\(u\)的父亲,当它转移到\(u\)时,所增加的合法子串数量只有这个括号加上从这个点到之前链上所有括号的匹配情况。
比如说我们加了一个右括号,我只需要往回搜索所有合法的每个链上的左括号即可。由此,我们需要用\(O(1)\)的时间检查每个左括号是否能对新加入的右括号作出贡献。
我们需要用一个栈记录所有没有匹配过的右括号。每次遇到一个右括号就把它进栈;每次遇到左括号如果栈空直接\(break\),因为再往前搜的字串一定经过这个不可能匹配到的左括号;否则把它出栈,如果出栈之后栈空说明从这个位置到新加入的位置恰好构成一个合法串,答案加一。
这个复杂度是\(O(n)\)的主要原因是对于每一个点必须扫描所有前面的点,这个过程效率实在太低。考虑优化这个过程。
对于每一个新进入栈的右括号,能对答案做出的贡献只有两种情况:
第一,找到了从当前位置往根节点方向走第一个没有匹配的左括号,这样从这个左括号到当前位置一定是合法的子串,因为这两个括号之间所有的左括号一定匹配了一个子串上的右括号。
第二,第一个没有匹配的左括号前面紧挨着它的合法子串可以和第一种情况的子串共同构成新的合法子串,而紧挨着当前链上第一个没有匹配的左括号的位置一定是固定的。
上面两种情况加起来得到了这个有括号的总计答案。
考虑设计\(dp\)转移状态。从第二部分入手,我们发现因为用来转移的位置是固定的(它紧挨着当前链上第一个没有匹配的左括号),当前加入点的位置也是固定的,考虑将前者作为后者的子问题。所以我们得到,用\(dp[i]\)表示从根节点走到点\(i\)的链上严格以\(i\)结尾的合法子串数目。
同时我们需要维护“第一个没有匹配的左括号的位置”,所以可以借助暴力的思路使用一个栈。用栈顶表示当前链上第一个没有匹配的左括号的位置,每次遇到一个左括号进栈并标记\(dp[i]=0\),遇到一个右括号时如果栈为空则标记\(dp[i]=0\)因为没有可以和它匹配的左括号;否则弹出栈顶,标记\(dp[i]=dp[fa[s[top]]]+1\)。那个加上的\(1\)表示这个加入的右括号和第一个没有匹配的左括号匹配上的子串,这个没有匹配的左括号是\(s[top]\);所以\(fa[s[top]]\)表示紧挨着它的第一个括号,即\(dp[fa[s[top]]]\)表示这个位置对应上述第二种情况。因为从这个点走到根节点且以这个点结尾的合法子串数目恰好每个合法子串都可以和上面那个加上的\(1\)组成一个新的子串。
比如说,从()(变成()(),新加入的右括号在对应那个没有匹配的左括号同时,这个括号对又能和前面匹配好的()组成新的合法串()()。
注意一个问题。因为我们全局只使用一个栈,所以我们当搜完一棵子树并回溯的时候必须恢复栈的状态到刚搜到这个点时的状态。所以我们考虑递归地恢复状态。对于每次搜索我们只有进栈/出栈两种可能,所以如果\(dp\)这个点进过栈,我们就将其弹出;如果出过栈我们就重新进栈。
代码。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<stack>
#define int long long
#define rep(i,a,n) for(register int i=a;i<=n;++i)
#define dwn(i,n,a) for(register int i=n;i>=a;--i)
using namespace std;
int n,head[500050],num,f[500050],dp[500050],ans;
stack<int> s;
char str[500050];
struct edge
{
int u,v,nxt;
}e[1000050];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0)putchar('-'),x=-x;
if(x==0)return;
write(x/10);
putchar('0'+x%10);
}
void add(int u,int v)
{
e[++num].u=u;e[num].v=v;
e[num].nxt=head[u];head[u]=num;
}
void DP(int x,int fa,int cost)
{
int flag=0;
if(str[x]=='(')s.push(x);
else
{
if(s.empty())dp[x]=0;
else
{
flag=s.top();//如果有过出栈操作就再进去
s.pop();
dp[x]=dp[f[flag]]+1;
}
}
for(register int st=head[x];~st;st=e[st].nxt)
{
int y=e[st].v;
if(y==fa)continue;
DP(y,x,cost+dp[x]);
}
if(flag)s.push(flag);//恢复
ans^=x*(cost+dp[x]);//cost记录不算这个点的答案
if(str[x]=='(')s.pop();//只有这个点是'('才进过栈
return;
}
signed main()
{
memset(head,-1,sizeof head);
n=read();
cin>>(str+1);
rep(i,2,n)
{
f[i]=read();
add(i,f[i]);
add(f[i],i);
}
DP(1,-1,0);
if(ans)write(ans);
else putchar('0');
return 0;
}
/*
7
(()))((
1 2 1 2 5 3
*/
2019CSP day1t2 括号树的更多相关文章
- [CSP-S 2019]括号树
[CSP-S 2019]括号树 源代码: #include<cstdio> #include<cctype> #include<vector> inline int ...
- P5658 括号树
P5658 括号树 题解 太菜了啥都不会写只能水5分数据 啥都不会写只能翻题解 题解大大我错了 我们手动找一下规律 我们设 w[ i ] 为从根节点到结点 i 对答案的贡献,也就是走到结点 i ,合 ...
- CSP2019 括号树
Description: 给定括号树,每个节点都是 ( 或 ) ,定义节点的权值为根到该节点的简单路径所构成的括号序列中不同合法子串的个数(子串需要连续,子串所在的位置不同即为不同.)与节点编号的乘积 ...
- 上午小测3 T1 括号序列 && luogu P5658 [CSP/S 2019 D1T2] 括号树 题解
前 言: 一直很想写这道括号树..毕竟是在去年折磨了我4个小时的题.... 上午小测3 T1 括号序列 前言: 原来这题是个dp啊...这几天出了好几道dp,我都没看出来,我竟然折磨菜. 考试的时候先 ...
- 2021.08.09 P5658 括号树(树形结构)
2021.08.09 P5658 括号树(树形结构) [P5658 CSP-S2019] 括号树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 太长,在链接中. 分析及代码 ...
- 【CSP-S 2019】【洛谷P5658】括号树【dfs】【二分】
题目: 题目链接:https://www.luogu.org/problem/P5658?contestId=24103 本题中合法括号串的定义如下: () 是合法括号串. 如果 A 是合法括号串,则 ...
- 【NOIP/CSP2019】D1T2 括号树
原题: 因为是NOIP题,所以首先先看特殊数据,前35分是一条长度不超过2000的链,N^2枚举所有子区间暴力check就能拿到分 其次可以思考特殊情况,一条链的情况怎么做 OI系列赛事的特殊性质分很 ...
- 【CSP2019】括号树 题解(递推+链表)
前言:抽时间做了做这道题,把学长送退役的题. ----------------- 题目链接 题目大意:定义$()$是合法括号串.如果$A,B$是合法括号串,那么$(AB),AB$为合法括号串.现给定根 ...
- 洛谷 P5658 [CSP-S2019] 括号树
链接: P5658 分析: 显然我们应该在dfs树的同时维护每个点的答案. 注意到第 \(u\) 个点的答案可以分成两部分,不包含 \(u\) 点时的答案,和加入 \(u\) 点后新增的答案,前者可以 ...
随机推荐
- Linux::mysql-connector-c++
.安装好boost. .从官网下载mysql connector c++版本. .解压,复制 include/jdbc/cppconn 文件夹复制,到/usr/local/include/cppcon ...
- 《Java并发编程实战》读书笔记-第5章 基础构建模块
同步容器类 同步容器类实现线程安全的方式:将所有状态封装起来,对每个公有方法使用同步,使得每一次只有一个线程可以访问.同步容器类包含:Vector.Hashtable.Collections.sync ...
- 2019.10.26 CSP%您赛第三场
\(CSP\)凉心模拟^_^ --题源\(lqx.lhc\)等各位蒟蒻 题目名称 比赛 传递消息 开关灯 源文件名 \(competition.cpp\) \(message.cpp\) \(ligh ...
- odoo联调
odoo联调(剑飞花 373500710) 1.准备工作 1.1.参考文章“odoo8.0+PyCharm4.5开发环境配置”配置好odoo开发环境 1.2.下载Chrome浏览器,安装. 1.3.下 ...
- restapi(8)- restapi-sql:用户自主的服务
学习函数式编程初衷是看到自己熟悉的oop编程语言和sql数据库在现代商业社会中前景暗淡,准备完全放弃windows技术栈转到分布式大数据技术领域的.但是在现实中理想总是不如人意,本来想在一个规模较小的 ...
- CodeForces - 1214D B2. Books Exchange (hard version)
题目链接:http://codeforces.com/problemset/problem/1249/B2 思路:用并查集模拟链表,把关系串联起来,如果成环,则满足题意.之后再用并查集合并一个链,一个 ...
- Rust入坑指南:千人千构
坑越来越深了,在坑里的同学让我看到你们的双手! 前面我们聊过了Rust最基本的几种数据类型.不知道你还记不记得,如果不记得可以先复习一下.上一个坑挖好以后,有同学私信我说坑太深了,下来的时候差点崴了脚 ...
- 树莓派apt报错:E: '\Release' 这个值对 APT::Default-Release 是无效的,因为在源里找不到这样的发行
E: '\jessie' 这个值对 APT::Default-Release 是无效的,因为在源里找不到这样的发行 开始尝试了各种方法, 换apt源, 改/etc/apt/apt.conf.d/10d ...
- 让搭建在 Github Pages 上的 Hexo 博客可以被 Google 搜索到
title: 让搭建在Github Pages上的Hexo博客可以被Google搜索到 date: 2019-05-30 23:35:44 tags: 配置 --- 准备工作 搭建好的博客 npm & ...
- Ubuntu 16.04下配置 Nginx 与 Node.js 以及服务的部署
第一步:安装nginx sudo apt-get update sudo apt-get install nginx 如果遇到依赖问题,尝试执行sudo apt-get -f install命令 第二 ...