P5658 括号树

Posted lpf-666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P5658 括号树相关的知识,希望对你有一定的参考价值。

P5658 括号树

经典的括号树,树形DP

(送我退役的括号树)

题意描述

相信我已经把这道题深深地记忆在我的脑海之中(想看出

第一年参加提高组,心情好自闭呀。

考场上只拿了链的35分的蒟蒻我,终于在靠后想出了正解。

算法分析

树形DP,但首先可以不用管它,先看部分分。

10~20pts

暴力O(N4)的。是个人都会写吧。

55pts

把链的分全拿了,观察我们暴力究竟慢在哪里了?

无非就是计算 1~i之中有多少个匹配的括号子序列。

观察数据,发现数据 5e5,所以只能O(N);

那么就要O(1)求解匹配的括号子序列,发现具有无后效性,于是果断DP.

过了很久很久…

手玩几组数据后我终于发现了规律:

if(这是右括号并可以匹配) a[i]=a[左括号的位置-1]+1;f[i]=f[i-1]+a[i];else 左括号入栈(当然入的是左括号的坐标)

100pts

终于到正解了。

果断

解决了链,有了稳稳的 55pts。想想好像可以实现化链成树,果断开正解。

很明显,我们解决链的做法在树里有很多行不通的地方。

细细想来,困难主要出现在这2个方面。

首先,你没法遍历整颗树的时候编号是连续的。这代表着我们 lst[i] = lst[t - 1] + 1 这样计算是完全行不通了。

其次,遍历一棵树必然会有递归和回溯。而处理链我们不考虑回溯,一直向下找就可以找完了。

冷静。

先看第一个问题

冷静分析一波,你会发现,虽然编号不连续了,但是你的括号序列一定是从父节点传递下来的!

仔细一想,我们发现,在链的情况里,为什么能用 lst[i] = lst[t - 1] + 1 计算贡献?其实,t?1 就是 t 的父亲节点!无非是 [t,i] 的括号序列继承了 [1,t?1]也就是 [1,fa[t]] 的括号序列!(fa[i]代表 i 的父亲)

然后我们惊喜的发现,这条定则对于树完全适用。

于是我们就可以修改一波原来的式子:

$lst[i] = lst[t - 1] + 1 ?> lst[x] = lst[fa[t]] + 1; $

perfect!

当然计算总答案也要修改:

(sum[i] = sum[i - 1] + lst[i]; ?> sum[x] = sum[fa[x]] + lst[x];)

这样我们就解决了问题1

接下来考虑解决第二个问题:

在树中遍历有回溯,回溯后栈里的信息可能就无法对应当前的版本...

其实这个问题很容易解决。由于每次回溯只回溯一层,所以我们在回溯的时候,执行我们递归时相反的操作即可!

比如,如果我们扫到右括号,递归时如果栈不为空,照理来说会弹出一个位置信息。

那么我们就可以记录这个信息,回溯的时候再把它压回去,又变成了我们当前的版本。

扫到左括号也一样。我们会压入一个位置信息,那么回溯时,直接弹出这个压入的信息就可以了!

其实这也相当于“复原”操作,让栈里的信息永远留在我们现在的状态!

没了

代码实现

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<string>
#include<vector>
using namespace std;

int n,fa[500050],zhan[500050],tot=0;
long long step[500050],sum[500050],ans;
char s[500050];
vector<int>side[500050];

void dfs(int x){
   int last=0;
   if(s[x]==')'){
     if(tot){
       last=zhan[tot];
       step[x]=step[fa[last]]+1;
       tot--;
     }
   }
   else if(s[x]=='(') zhan[++tot]=x;
   sum[x]=sum[fa[x]]+step[x];
   for(int i=0;i<side[x].size();i++) dfs(side[x][i]);
   //回溯
   if(last) zhan[++tot]=last;
   else if(tot) tot--;
   return;
 }

int main(){
   scanf("%d",&n);
   cin>>s+1;
   for(int i=2;i<=n;i++){
     scanf("%d",&fa[i]);
     side[fa[i]].push_back(i);
   }
   dfs(1); 
   for(long long i=1;i<=n;i++){
     ans^=sum[i]*i;
     //printf("%lld
",sum[i]);
   }
   printf("%lld",ans);
   //system("pause");
   return 0;
}

结语

一步一步分析,这道题是不是就没有这么难了?

这也告诉了我们,考场上,一开始绝对不要先想正解,先看一看部分分,对你想正解有帮助哦!

很明显,我们这次解题的步骤就是由 暴力 -> 链 -> 正解的!

没了。。。

以上是关于P5658 括号树的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P5658 括号树|搜索+递推

如何理解这段代码片段中的两对括号?

vue2.0 代码功能片段

asp.net 使用正则表达式验证包含打开/关闭括号片段的属性字符串

当我在括号中调用它时,Thymeleaf 不会解析我的应用程序中的片段。这是为啥?

gym102889J线段树维护最大最小前缀和判断合法括号序列