zoukankan      html  css  js  c++  java
  • CSP-S2019 D1T2 括号树

    CSP-S2019 D1T2 括号树

    洛谷传送门

    题目背景

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

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

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

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

    题目描述

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

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

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

    显然 s_is**i 是个括号串,但不一定是合法括号串,因此现在小 Q 想对所有的 ii(1leq ileq n1≤in)求出,s_is**i 中有多少个互不相同的子串合法括号串

    这个问题难倒了小 Q,他只好向你求助。设 s_is**i 共有 k_ik**i 个不同子串是合法括号串, 你只需要告诉小 Q 所有 i imes k_ii×k**i 的异或和,即:

    (1 imes k_1) ext{xor} (2 imes k_2) ext{xor} (3 imes k_3) ext{xor} cdots ext{xor} (n imes k_n)(1×k1) xor (2×k2) xor (3×k3) xor ⋯ xor (n×k**n)

    其中 xorxor 是位异或运算。

    输入格式

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

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

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

    输出格式

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


    题解:

    还是先放回忆杀。

    用了将近一个点来切T1,但是最后还是因为没开ull见了5分的祖宗。剩下的时间基本都在淦T2,因为T3基本上是毫无头绪。于是T2还是挂了。总之当时考场思路是一团乱麻,根本不知道自己想要写什么。还是蒟蒻太菜了。

    细细思考,题意需要我们干什么?首先,处理出从1号点到每个点的括号序列,然后统计它的合法子串数目,最后异或和。

    主要卡在如何统计合法子串数目(废话)。

    这种统计数目的题,肯定要先试一试递推。那么,递推的转移过程,其实就是看一个新的半括号进场的时候,对答案有什么样子的影响。可以发现的性质是,一个合法括号串一定是一个平衡括号串。如果是左括号,肯定还没构成合法括号串,如果是右括号,首先要看它平衡掉了哪个左括号。这样才能进一步确定这个平衡的区间到底是哪个。

    更进一步地,对于题目给出的合法括号串定义3,对于一个合法括号串的前面的所有合法括号串,皆可以与当前的括号串构成新的合法子串。这也是统计的时候唯一需要考虑加法原理的地方。

    所以可以得出一个状态转移的方程:设(cnt[i])表示以从根到(i)的合法串的数目(注意,并非合法子串)。(last[i])表示离i最近的未被匹配的左括号位置。

    即有:

    [cnt[i]=cnt[fa[last[i]]]+1 ]

    新匹配到的当然是一个新子串,其之前的合法串数目和它自己都会构成一个新的合法子串。于是更新cnt数组,同时,因为last已经被换掉了,所以也要更新last,其更新方法为:

    [last[i]=last[fa[last[i]]] ]

    比较好理解的。

    先放代码:

    #include<cstdio>
    #define ll long long
    using namespace std;
    const int maxn=5*1e5+10;
    int n;
    ll ans;
    char s[maxn];
    int tot,to[maxn],head[maxn],nxt[maxn],fa[maxn];
    ll cnt[maxn],last[maxn];
    //cnt[x]表示根节点到x点的合法串个数,last[x]表示距离x节点最近的未匹配左括号。
    void add(int x,int y)
    {
    	to[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    void dfs(int x)
    {
    	last[x]=last[fa[x]];
    	if(s[x]=='(')
    		last[x]=x;
    	else if(last[x])
    		cnt[x]=cnt[fa[last[x]]]+1,last[x]=last[fa[last[x]]];
    	for(int i=head[x];i;i=nxt[i])
    		dfs(to[i]);
    }
    int main()  
    {
    	scanf("%d%s",&n,s+1);
    	for(int i=2;i<=n;i++)
    	{
    		int x;
    		scanf("%d",&x);
    		fa[i]=x;
    		add(x,i);
    	}
    	dfs(1);
    	ans=cnt[1];
    	for(int i=2;i<=n;i++)
    		cnt[i]+=cnt[fa[i]],ans^=(i*cnt[i]);
    	printf("%lld",ans);
    	return 0;
    }
    

    这个算法的精妙之处就在于:它的last数组采用了下传的形式,大大减少了时间复杂度。也就是在函数开始之前先把上面的信息复制下来,这样就免去了回溯的麻烦,以及,快速找到需要的信息。

    妙哉!

  • 相关阅读:
    BZOJ 3506 机械排序臂 splay
    BZOJ 2843 LCT
    BZOJ 3669 魔法森林
    BZOJ 2049 LCT
    BZOJ 3223 文艺平衡树 splay
    BZOJ 1433 假期的宿舍 二分图匹配
    BZOJ 1051 受欢迎的牛 强连通块
    BZOJ 1503 郁闷的出纳员 treap
    BZOJ 1096 ZJOI2007 仓库设计 斜率优化dp
    BZOJ 1396: 识别子串( 后缀数组 + 线段树 )
  • 原文地址:https://www.cnblogs.com/fusiwei/p/13750383.html
Copyright © 2011-2022 走看看