题目背景

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

  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

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 括号树的更多相关文章

  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. 深入了解一下HTTP缓存机制

    HTTP 缓存机制作为 web 性能优化的重要手段,是Web 开发知识体系库中的一个基础环节,但是对于很多学习者来说,仅仅只是知道浏览器会对请求的静态文件进行缓存,但是为什么被缓存,缓存是怎样生效的, ...

  2. Git推送到多个远程仓库

    Git推送到多个远程仓库 Grey 原文地址 准备工作 在码云和Github上分别新建两个不包括任何文件的空仓库(若是两个已经有文件的仓库,请参见关联已经存在的项目) https://github.c ...

  3. opencv::基于距离变换与分水岭的图像分割

    什么是图像分割 图像分割(Image Segmentation)是图像处理最重要的处理手段之一 图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素. 根 ...

  4. [七年技术总结系列][理论篇]-RBAC权限模型由浅入深

    权限部分将分两章介绍,第一章由浅入深介绍权限理论知识及应用,第二章介绍具体实现.后期再讲述中间件的使用时,还会插入一些权限内容,本质上属于中间件的应用. 权限模块是业务系统最常见.最基本的子集.本章假 ...

  5. ESP32 开发之旅① 走进ESP32的世界 安装开发环境

    1.前言     欢迎大家来到ESP32的世界,从现在开始,笔者将带领大家慢慢揭开ESP32神秘的面纱.     在学习ESP32之前,博主希望读者能有ESP8266的学习基础(ESP32 Wifi模 ...

  6. 玩转PubSubClient MQTT库

    1.前言     在ESP8266学习系列中,博主一直使用HTTP协议.HTTP连接属于短连接,而在物联网应用中,广泛应用的却是MQTT协议.所以,本篇我们将学习Arduino平台上的MQTT实现库 ...

  7. kali2019里安装Burp Suite安装破解版加汉化版

    Burpsuite是一个强大web漏洞挖掘工具,截断代理,解码和编码,Fuzzy进行各种注入和暴力破解 插件扩展,有多个模块 Burp Suite没有中文版的,我英语又不好,我虽然精通Burp Sui ...

  8. Android_Fragment

    (一) Faragment有自己的生命周期 Fragment依赖于Activity Fragmen通过getActivity()可以获取所在Activity:Activity通过FragmentMan ...

  9. emacs考场短配置

    (set-background-color "gray15") (set-foreground-color "gray") ;;设置颜色 (global-set ...

  10. 爬虫之scrapy简介

    原始的爬虫流程:效率低.同步.阻塞 scrapy执行流程:效率高.异步.非阻塞 scrapy的概念 scrapy是一个爬虫框架 开发速度快 稳定性高 性能优越 scrapy的流程 1. 爬虫模块(Sp ...