题目背景

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

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

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

  1. 字符串 S 的子串是 S连续的任意个字符组成的字符串。S 的子串可用起始位置 \(l\) 与终止位置 \(r\) 来表示,记为 S (l, r)(\(1 \leq l \leq r \leq |S |\)\(|S |\) 表示 \(S\) 的长度)。
  2. 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

  1. 5
  2. (()()
  3. 1 1 2 2

输出 #1

  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\)这个点进过栈,我们就将其弹出;如果出过栈我们就重新进栈。

代码。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<cmath>
  5. #include<cctype>
  6. #include<algorithm>
  7. #include<stack>
  8. #define int long long
  9. #define rep(i,a,n) for(register int i=a;i<=n;++i)
  10. #define dwn(i,n,a) for(register int i=n;i>=a;--i)
  11. using namespace std;
  12. int n,head[500050],num,f[500050],dp[500050],ans;
  13. stack<int> s;
  14. char str[500050];
  15. struct edge
  16. {
  17. int u,v,nxt;
  18. }e[1000050];
  19. inline int read()
  20. {
  21. int x=0,f=1;
  22. char ch=getchar();
  23. while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
  24. while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  25. return x*f;
  26. }
  27. void write(int x)
  28. {
  29. if(x<0)putchar('-'),x=-x;
  30. if(x==0)return;
  31. write(x/10);
  32. putchar('0'+x%10);
  33. }
  34. void add(int u,int v)
  35. {
  36. e[++num].u=u;e[num].v=v;
  37. e[num].nxt=head[u];head[u]=num;
  38. }
  39. void DP(int x,int fa,int cost)
  40. {
  41. int flag=0;
  42. if(str[x]=='(')s.push(x);
  43. else
  44. {
  45. if(s.empty())dp[x]=0;
  46. else
  47. {
  48. flag=s.top();//如果有过出栈操作就再进去
  49. s.pop();
  50. dp[x]=dp[f[flag]]+1;
  51. }
  52. }
  53. for(register int st=head[x];~st;st=e[st].nxt)
  54. {
  55. int y=e[st].v;
  56. if(y==fa)continue;
  57. DP(y,x,cost+dp[x]);
  58. }
  59. if(flag)s.push(flag);//恢复
  60. ans^=x*(cost+dp[x]);//cost记录不算这个点的答案
  61. if(str[x]=='(')s.pop();//只有这个点是'('才进过栈
  62. return;
  63. }
  64. signed main()
  65. {
  66. memset(head,-1,sizeof head);
  67. n=read();
  68. cin>>(str+1);
  69. rep(i,2,n)
  70. {
  71. f[i]=read();
  72. add(i,f[i]);
  73. add(f[i],i);
  74. }
  75. DP(1,-1,0);
  76. if(ans)write(ans);
  77. else putchar('0');
  78. return 0;
  79. }
  80. /*
  81. 7
  82. (()))((
  83. 1 2 1 2 5 3
  84. */

2019CSP day1t2 括号树的更多相关文章

  1. [CSP-S 2019]括号树

    [CSP-S 2019]括号树 源代码: #include<cstdio> #include<cctype> #include<vector> inline int ...

  2. P5658 括号树

    P5658 括号树 题解 太菜了啥都不会写只能水5分数据 啥都不会写只能翻题解  题解大大我错了 我们手动找一下规律 我们设 w[ i ] 为从根节点到结点 i 对答案的贡献,也就是走到结点 i ,合 ...

  3. CSP2019 括号树

    Description: 给定括号树,每个节点都是 ( 或 ) ,定义节点的权值为根到该节点的简单路径所构成的括号序列中不同合法子串的个数(子串需要连续,子串所在的位置不同即为不同.)与节点编号的乘积 ...

  4. 上午小测3 T1 括号序列 && luogu P5658 [CSP/S 2019 D1T2] 括号树 题解

    前 言: 一直很想写这道括号树..毕竟是在去年折磨了我4个小时的题.... 上午小测3 T1 括号序列 前言: 原来这题是个dp啊...这几天出了好几道dp,我都没看出来,我竟然折磨菜. 考试的时候先 ...

  5. 2021.08.09 P5658 括号树(树形结构)

    2021.08.09 P5658 括号树(树形结构) [P5658 CSP-S2019] 括号树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 太长,在链接中. 分析及代码 ...

  6. 【CSP-S 2019】【洛谷P5658】括号树【dfs】【二分】

    题目: 题目链接:https://www.luogu.org/problem/P5658?contestId=24103 本题中合法括号串的定义如下: () 是合法括号串. 如果 A 是合法括号串,则 ...

  7. 【NOIP/CSP2019】D1T2 括号树

    原题: 因为是NOIP题,所以首先先看特殊数据,前35分是一条长度不超过2000的链,N^2枚举所有子区间暴力check就能拿到分 其次可以思考特殊情况,一条链的情况怎么做 OI系列赛事的特殊性质分很 ...

  8. 【CSP2019】括号树 题解(递推+链表)

    前言:抽时间做了做这道题,把学长送退役的题. ----------------- 题目链接 题目大意:定义$()$是合法括号串.如果$A,B$是合法括号串,那么$(AB),AB$为合法括号串.现给定根 ...

  9. 洛谷 P5658 [CSP-S2019] 括号树

    链接: P5658 分析: 显然我们应该在dfs树的同时维护每个点的答案. 注意到第 \(u\) 个点的答案可以分成两部分,不包含 \(u\) 点时的答案,和加入 \(u\) 点后新增的答案,前者可以 ...

随机推荐

  1. selenium-模块概述(1)

    Selenium是一个用于Web应用程序自动化测试工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样. 1.目录结构如下: D:\soft\python36\Lib\site-pa ...

  2. 对比js库分枝Jquery和js获取对象的方式

    一.Jquery和JS的认识 对于这点不谈详细,但能有一个能有一个全面的印象,Jquery本质上也是JS,只不过用一句话概括就是   “write letter and do more”,写的更少,做 ...

  3. JavaScript ES6函数式编程(三):函子

    前面二篇学习了函数式编程的基本概念和常见用法.今天,我们来学习函数式编程的最后一个概念--函子(Functor). 相信有一部分同学对这个概念很陌生,毕竟现在已经有很多成熟的轮子,基本能满足我们日常的 ...

  4. MongoDB-系统时钟跳变引发的风波

    目录 背景 一. 对 oplog 的影响 oplog 原理 二.主备倒换 小结 声明:本文同步发表于 MongoDB 中文社区,传送门: http://www.mongoing.com/archive ...

  5. 你的 Java 并发程序 Bug,100% 是这几个原因造成的

    可见性问题 可见性是指一个线程对共享变量进行了修改,其他线程能够立马看到该共享变量更新后的值,这视乎是一个合情合理的要求,但是在多线程的情况下,可能就要让你失望了,由于每个 CPU 都有自己的缓存,每 ...

  6. epoll(2) 源码分析

    epoll(2) 源码分析 文本内核代码取自 5.0.18 版本,和上一篇文章中的版本不同是因为另一个电脑出了问题,但是总体差异不大. 引子留下的问题 关键数据结构 提供的系统调用 就绪事件相关逻辑 ...

  7. unity message

    再用unity进行开发过程中,不可避免的用到消息的传递问题,以下介绍几种消息传递的方法: (一)拖动赋值 此方法即为最普通的方法,即把需要引用的游戏物体或者需要引用的组件拖动到相关公有变量的槽上,然后 ...

  8. Day 3,学习的知识点

    年龄 如何判断是否未成年人 age = input('请输入你的年龄:')#input=输入age = int(age)#int=转化为整型if age < 18:    print('小妹妹你 ...

  9. virtual与override的使用

    在函数的声明中,当有“virtual”修饰的时候,和没有virtual有什么区别呢?最重要的一点就是调用实例的函数是在编译的时候确定还是在运行的时候确定,virtual函数是在运行的时候来确定具体调用 ...

  10. egg-middleware 中间件

    Middleware 中间件 Egg 的中间件形式和 Koa 的中间件形式是一样的,都是基于洋葱圈模型.每次我们编写一个中间件,就相当于在洋葱外面包了一层. 编写中间件 写法 我们先来通过编写一个简单 ...