CSP-S2 2019 D1T2 括号树题解

说在前面的话

谨以此篇题解,纪念我初中的两年\(OI\)生涯以及不长不短的停课时光。

但愿高中还能够继续学习\(OI\)吧,也衷心希望其他\(OIer\)不要再犯类似我的错误。

题意

原题链接

给定一棵有\(n\)个节点的树,每个节点对应一个括号(左或右),需要求出从该节点到根节点的简单路径所构成的括号串中合法括号子串的数量。
其中合法括号串的定义是这样的

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

其中\(n\le 5\times 10^5\)

题解

1. 初步思路

令\(cnt_i\)表示从\(i\)节点到根节点所构成的括号串中合法括号子串的数量,\(f\)表示\(i\)节点的父亲,则有一个很显然的结论:
\[ cnt_i=cnt_f+(\text{以i节点为结尾的合法括号子串的个数}) \]
这个结论应该无需证明了……这是解题的关键,于是我们就只需要考虑以\(i\)节点为结尾的合法括号子串的个数了(设其为\(t_i\))。

2. 统计答案

先考虑怎么暴力统计,显然直接从\(i\)节点往上跳,统计合法括号子串数即可,这样做是\(O(n_2)\)的。

再考虑给出的合法括号串的定义:“如果AB是合法括号串,则AB是合法括号串”

举个例子,考虑暴力的过程,如果以\(i\)为结尾的括号串是()()()(),我们总共了统计了\(4\)个,但其实,我们只需要统计最右边(dfs序最大)的那一对合法的括号,而左边的三对括号就相当于\(t_{f_f}\)(即以\(f_f\)为结尾的合法括号串的数量),这是因为最左边的括号串()合法,右边的三个括号串()()()()()()合法,联系上面的性质,所以将它们连起来同样合法。

到这一步之后,貌似大多数人都用栈来做,我在考场上\(yy\)出了一种奇♂妙的方法,在这里分享一下。

于是我们设\(re_i\)为满足从\(i\)节点到该节点所构成的括号串为合法括号串,且深度最大的节点,得到\(cnt_i\)的表达式
\[ cnt_i=cnt_f+t_{f_{re_i}}+1 \]
结合一下先前的例子()()()(),\(cnt_f\)不用多讲,\(1\)表示的是以\(re_i\)为开头以\(i\)为结尾的合法括号串(即例子中最左边的的()),\(t_{f_{re_i}}\)就是例子中右边的三个合法括号串()()()()()(),至于这三个括号串为什么要记入答案上文已讨论。

再结合一下代码看看:

cnt[x]+=cnt[f],fa[x]=f;
if (a[x]==1) // 只有a[x]==1(即括号为')')才有可能存在以x为结尾的合法括号串
{
    while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]]; // 找到re[x]
    if (f==0||a[f]==1) re[x]=-1; // re[x]需合法
    else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1; // 统计答案。cnt[fa[f]]-cnt[fa[fa[f]]]就等于上文中的t[fa[f]]
}
else re[x]=-1;

经验教训

笔者在考场做这道题时,将上文中的while打成了if,于是(洛谷自测)\(100 \to 10\)

其实是没有考虑到这种情况((())())。(至于为什么错可以手玩一下)

然后就开开心心\(\text{AFO}\)搞文化课了

这里以亲身教训提醒大家,一定要注意细节!考虑情况一定要充分!不要重蹈我的覆辙!

祝大家人人取得满意的成绩(我是拿不到了)

代码

其实代码就很短啦,\(qwq\)。

#include <stdio.h>

using namespace std;

template <typename T> inline void Read(T &t)
{
    int c=getchar(),f=0;
    for (;c<'0'||c>'9';c=getchar()) f=(c=='-');
    for (t=0;c>='0'&&c<='9';c=getchar()) t=(t<<3)+(t<<1)+(c^48);
    if (f) t=-t;
}

typedef long long ll;
const int N=5e5+5;

int n,tot,head[N],a[N],fa[N],re[N];
ll ans,cnt[N];
char temp[N];

struct Edge
{
    int to,next;
    void add(int x, int y) { to=y,next=head[x],head[x]=tot; }
} e[N<<1];

void dfs(int x, int f)
{
    cnt[x]+=cnt[f],fa[x]=f;
    if (a[x]==1)
    {
        while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]];
        if (f==0||a[f]==1) re[x]=-1;
        else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1;
    }
    else re[x]=-1;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        dfs(v,x);
    }
}   

signed main()
{
    Read(n);
    scanf("%s",temp);
    for (int i=1;i<=n;i++)
        a[i]=(temp[i-1]=='('?-1:1);
    for (int i=2,f;i<=n;i++) Read(f),e[++tot].add(f,i);

    re[0]=-1;
    dfs(1,0);

    for (ll i=1;i<=n;i++) ans^=(cnt[i]*i);
    printf("%lld\n",ans);

    return 0;
}

结语

笔者大概率是\(\text{AFO}\)了,但还是希望这篇题解能给做出或没有做出这道题的人带来一些帮助,也算是我\(OI\)生涯的回光返照

去搞文化课准备中考了。

原文地址:https://www.cnblogs.com/asd369-blog/p/CSP-S2-2019-brackets.html

时间: 2024-08-29 17:08:34

CSP-S2 2019 D1T2 括号树题解的相关文章

P5658 括号树

P5658 括号树 题解 太菜了啥都不会写只能水5分数据 啥都不会写只能翻题解  题解大大我错了 我们手动找一下规律 我们设 w[ i ] 为从根节点到结点 i 对答案的贡献,也就是走到结点 i ,合法括号串又多了几个 sum[ i ] 为从根节点到结点 i 总共合法括号串数 ()()() w[i] 依次为 0  1  0  2  0  3 sum[i] 依次为 0  1  1  3  3  6 ())() w[i] 依次为 0  1  0  0  1 sum[i] 依次为 0  1  1  1

CSP-S 2019 括号树

\(\text{括号树}\) 本题中合法括号串的定义如下: \(()\) 是合法括号串. 如果 \(A\) 是合法括号串,则\((A)\) 是合法括号串. 如果 \(A\),\(B\) 是合法括号串,则 \(AB\) 是合法括号串. 小 \(Q\) 定义 \(s_i\)为:将根结点到\(i\)号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串. 设\(s_i\)共有\(k_i\)个不同子串是合法括号串, 你只需要告诉小 Q 所有 \(i * k_i\)的\(xor\)值 subtask

Vijos1448校门外的树 题解

Vijos1448校门外的树 题解 描述: 校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的…… 如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作: K=1,K=1,读入l.r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同 K=2,读入l,r表示询问l~r之间能见到多少种树 (l,r>0) 输入格式: 第一行n,m表示道路总长为n,共有m个操作 接下来m行为m个操作 输出格式: 对于每个k=2输出一个答案 样例输入:

POJ 2528 Mayor&#39;s posters 离散化和线段树题解

本题就是要往墙上贴海报,问最后有多少可见的海报. 其实本题的难点并不是线段树,而是离散化. 因为数据很大,直接按原始数据计算那么就会爆内存和时间的. 故此需要把数据离散化. 比如有海报1 6   7 9   20 100  5 1000的原始数据,直接计算需要1-1000的内存,离散化之后只需要8内存,因为只有4组数据8个数. 本题更进一步高级一点的离散化就是需要把不相邻的两个数据插入一个数值,表示有空白的地方,不是所有海报都覆盖到的. 比如上面的数据要离散为:1 2  5 6  7 8 9 1

POJ 2528 Mayor&amp;#39;s posters 离散化和线段树题解

本题就是要往墙上贴海报,问最后有多少可见的海报. 事实上本题的难点并非线段树,而是离散化. 由于数据非常大,直接按原始数据计算那么就会爆内存和时间的. 故此须要把数据离散化. 比方有海报1 6   7 9   20 100  5 1000的原始数据.直接计算须要1-1000的内存,离散化之后仅仅须要8内存,由于仅仅有4组数据8个数. 本题更进一步高级一点的离散化就是须要把不相邻的两个数据插入一个数值.表示有空白的地方,不是全部海报都覆盖到的. 比方上面的数据要离散为:1 2  5 6  7 8

BZOJ 3211 花神游历各国 线段树题解

BZOJ 3211 花神游历各国 线段树题解 3211: 花神游历各国 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 2551  Solved: 946[Submit][Status][Discuss] Description Input Output 每次x=1时,每行一个整数,表示这次旅行的开心度 Sample Input 4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4 Sample Output 101

HDU 3911 Black And White 分段树 题解

Problem Description There are a bunch of stones on the beach; Stone color is white or black. Little Sheep has a magic brush, she can change the color of a continuous stone, black to white, white to black. Little Sheep like black very much, so she wan

比赛之字典树题解

这道题第一眼看见题目所给的时间就有一种预感,仅仅是600ms,运行的算法复杂度稍微高一点就会超时.那么我首先是犯傻想偷偷懒,直接是调用一个系统库函数strstr(),希望它能够完成自己的题目,但是显然是超时的.百度了一下它的实现方法是直接采用没有优化的算法,复杂度是最高的.但是由于自己压根就不会写字典树,所以还是抱着一个侥幸的心态去用KMP算法来实现,结果还是铁铁的超时.那么最后的实现应该是通过什么方式呢? 很显然,这道题是一个很裸的字典树题,直接使用字典树的方式解决是最好的.以后也要将这些最基

线段树&#183;题解报告

线段树·题解报告 参考资料 ·课件 线段树 --刘汝佳 统计的力量,线段树全接触 --张昆玮 ·Blog [完全版]线段树 从普通线段树到zkw线段树 [总结][数据结构]ZKW线段树详解 选题目录 · Hdu1166 敌兵布阵(单点更新,区间求和) · Hdu1754 I Hate It(单点更新,RMQ) · Hdu3308 LCIS(单点更新,区间并) · Poj3468 A Simple Problem with Integers(区间加减,区间求和) · Poj2777 Count C