CSP-S2019提高组day1-T2:括号树

题目链接

[CSP-S2019] 括号树

题目描述

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

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

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

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

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

小 Q 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 n n n 的树,树上结点从 1 ∼ n 1 \sim n 1n 编号, 1 1 1 号结点为树的根。除 1 1 1 号结点外,每个结点有一个父亲结点, u u u 2 ≤ u ≤ n 2 \leq u \leq n 2un)号结点的父亲为 f u f_u fu 1 ≤ f u < u 1 ≤ f_u < u 1fu<u)号结点。

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

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

这个问题难倒了小 Q,他只好向你求助。设 s i s_i si 共有 k i k_i ki 个不同子串是合法括号串, 你只需要告诉小 Q 所有 i × k i i \times k_i i×ki 的异或和,即:
( 1 × k 1 )  xor  ( 2 × k 2 )  xor  ( 3 × k 3 )  xor  ⋯  xor  ( n × k n ) (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) (1×k1) xor (2×k2) xor (3×k3) xor  xor (n×kn)
其中 x o r xor xor 是位异或运算。

输入格式

第一行一个整数 n n n,表示树的大小。

第二行一个长为 n n n 的由() 组成的括号串,第 i i i 个括号表示 i i i 号结点上的括号。

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

输出格式

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

样例 #1

样例输入 #1

5
(()()
1 1 2 2

样例输出 #1

6

提示

【样例解释1】

树的形态如下图:
在这里插入图片描述

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

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

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

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

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

【数据范围】

在这里插入图片描述

算法思想

根据题目描述,要求的是树中从根结点到 i i i 号结点组成的括号串 s i s_i si中,合法括号串的个数。示例参考提示【样例解释1】。

对于任意括号串 s i s_i si如下图所示:
在这里插入图片描述
要计算括号串 s i s_i si中合法括号串的个数,不妨设为 f ( i ) f(i) f(i),从最后一步分析,也就是根据 i i i号结点的选择分成两种情况:

  • 不选择 i i i号结点,那么方案数等于括号串 s i − 1 s_{i-1} si1中合法括号串的个数,即 f ( i − 1 ) f(i-1) f(i1)
  • 选择 i i i号结点(前提是 i i i号结点是)),那么 i i i结尾的合法括号串的个数如何计算呢,不妨设其为 g ( i ) g(i) g(i)
    • 首先找到与 i i i号结点匹配的(的位置,不妨设为 j j j(如上图所示),那么从 j j j i i i就是一组合法的括号串
    • 其次考虑 j j j之前合法括号串的个数,即以 j − 1 j-1 j1结尾的合法括号串的个数,就是 g [ j − 1 ] g[j-1] g[j1]
    • 可得以 i i i结尾的合法括号串的个数,就是在以 j − 1 j-1 j1结尾的合法括号串后添加了 1 1 1组,即 g [ i ] = g [ j − 1 ] + 1 g[i]=g[j-1]+1 g[i]=g[j1]+1

那么, f ( i ) = f ( i − 1 ) + g [ i ] f(i)=f(i-1)+g[i] f(i)=f(i1)+g[i],其中 g [ i ] g[i] g[i]表示 i i i结尾的合法括号串的个数 g [ i ] = g [ j − 1 ] + 1 g[i]=g[j-1]+1 g[i]=g[j1]+1 j j j是与 i i i匹配的左括号(的位置。

进一步分析, j − 1 j-1 j1结尾的合法括号串的个数 g [ j − 1 ] g[j-1] g[j1],其实就是从根节点到 j − 1 j-1 j1号结点的所有合法括号串中去掉不选择 j − 1 j-1 j1号结点的方案数,即 g ( j − 1 ) = f ( j − 1 ) − f ( j − 2 ) g(j-1)=f(j-1)-f(j-2) g(j1)=f(j1)f(j2)

综上所述, f ( i ) = f ( i − 1 ) + f ( j − 1 ) − f ( j − 2 ) + 1 f(i)=f(i-1)+f(j-1)-f(j-2)+1 f(i)=f(i1)+f(j1)f(j2)+1,其中 j j j是与 i i i匹配的左括号(的位置。

通过上面的分析,要想进行计算还要解决两个问题:

  • 在树上如何得到括号串 i i i位置的上一个结点 i − 1 i-1 i1

在树上 i i i位置的上一个结点,就是 i i i的父节点,可以设置一个数组p[i]来存储i的父节点

  • 如何得到与 i i i匹配的左括号(的位置 j j j

括号匹配,可以通过栈来实现。如果i位置是左括号,直接入栈,如果i位置是右括号,则栈顶就是与之匹配的左括号的位置。

下面从动态规划的角度将上述分析整理一下。

状态表示

  • f [ i ] f[i] f[i]表示树中从根结点到 i i i 号结点组成的括号串 s i s_i si中合法括号串的个数

最终答案 = ( 1 × f [ 1 ] )  xor  ( 2 × f [ 2 ] )  xor  ( 3 × f [ 3 ] )  xor  ⋯  xor  ( n × f [ n ] ) = (1 \times f[1])\ \text{xor}\ (2 \times f[2])\ \text{xor}\ (3 \times f[3])\ \text{xor}\ \cdots\ \text{xor}\ (n \times f[n]) =(1×f[1]) xor (2×f[2]) xor (3×f[3]) xor  xor (n×f[n])。注意 n n n的范围很大( n ≤ 5 × 1 0 5 n\le5\times10^5 n5×105),会爆int

状态计算

从最后一步分析,根据 i i i号结点的选择分成两种情况:

  • 不选择 i i i号结点,那么方案数等于括号串 s i − 1 s_{i-1} si1中合法括号串的个数,即 f [ p [ i ] ] f[p[i]] f[p[i]],其中 p [ i ] p[i] p[i]表示 i i i的父结点,也就是括号串中 i i i之前的结点
  • 选择 i i i号结点(前提是 i i i号结点是)),先找到与 i i i匹配的左括号的位置 j j j,其父节点为 p [ j ] p[j] p[j],那么以 i i i结尾的合法括号串的个数 f [ p [ j ] ] − f [ p [ p [ j ] ] ] + 1 f[p[j]]-f[p[p[j]]]+1 f[p[j]]f[p[p[j]]]+1

那么, f [ i ] = f [ p [ i ] ] + f [ p [ j ] ] − f [ p [ p [ j ] ] ] + 1 f[i]=f[p[i]]+f[p[j]]-f[p[p[j]]]+1 f[i]=f[p[i]]+f[p[j]]f[p[p[j]]]+1

初始状态

根节点编号为 1 1 1,根结点自己组成的合法括号串的个数为 0 0 0,即 f [ 1 ] = 0 f[1]=0 f[1]=0

时间复杂度

  • 状态数为 n n n
  • 每个结点的状态只会计算一次,时间复杂度 O ( 1 ) O(1) O(1)

总的时间复杂度为 O ( n ) O(n) O(n)

代码实现

#include <iostream>
#include <vector>
using namespace std;
const int N = 5e5 + 10;
typedef long long LL;
char s[N];
int p[N], stk[N], top;
//f[i]表示树中从根结点到i号结点组成的括号串中合法的方案数
LL f[N]; 
vector<int> g[N]; //邻接表
void dfs(int i)
{
    if(s[i] == '(')
    {
        stk[++ top] = i; //左括号入栈
        f[i] = f[p[i]]; //不选择i号结点
        for(int k : g[i]) 
            dfs(k); //递归处理子结点
        top --; //回溯,恢复现场
    }
    else //右括号
    {
        if(top == 0) //栈空,没有与之匹配的左括号
        {
            f[i] = f[p[i]]; //不选择i号结点
            for(int k : g[i]) 
                dfs(k); //递归处理子结点
        }
        else
        {
            int j = stk[top --]; //从栈顶取出与i匹配的右括号位置
            f[i] = f[p[i]] + f[p[j]] - f[p[p[j]]] + 1; //状态计算
            for(int k : g[i]) 
                dfs(k); //递归处理子结点
            stk[++ top] = j; //回溯,恢复现场
        }
    }
}
int main()
{
    int n;
    scanf("%d%s", &n, s + 1);
    for(int i = 2; i <= n; i ++)
    {
        scanf("%d", &p[i]); //输入i的父结点
        g[p[i]].push_back(i); //将i添加到a的子结点中
    }
    
    dfs(1);
    
    LL ans = 0;
    for(int i = 1; i <= n; i ++) ans ^= i * f[i];
    printf("%lld\n", ans);
    return 0;
}

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值