括号树

括号树

P5658 括号树

题目简述:

PS:具体描述请见上面的题目链接

给定n表示树的大小,然后给出每个点是左括号还是右括号,再给出2~n-1每个点的父节点f的编号(1一定为根节点)

定义si:将根结点到i号结点的简单路径上的括号,按节点经过顺序依次排列组成的字符串

要求对所有的i求出,si中有多少个互不相同的子串是合法括号串

合法字符串的定义:

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

设si共有ki个不同子串是合法括号串,你只需要求出所有i×ki的异或和


数据范围:

数据范围


算法&知识点:

链&树、栈、DFS


解题历程

因为是考试的题,我们就用解决考试题的思路来解决这道题

  • 读懂题意
  1. 求出所有i×ki的异或和,注意是先相乘再异或和
  2. 了解所有合法字符串的样子:()、(())、()()
  • 分析数据范围

看到特殊性质一栏,发现有接近一半的数据满足“fi=i-1”,这是啊!!相对于直接处理复杂的树,先将链的部分分拿到岂不是更稳?

  • 处理链的部分分
  1. 直接处理链好像也没什么思路,那我们就先用暴力来模拟处理链的情况,如下(思路就是纯枚举,纯暴力,20pts):
/* 接近O(N^4)的复杂度,只能跑过前四个点,20pts */

#include <bits/stdc++.h>
using namespace std;
int n,x,ans,sum[510005];
char op[510005];
string ops;

inline bool check(string s) { //check子函数,开栈来判断括号是否匹配 
	stack<char> fh;
	for(register int i=0;i<s.length();i++) {
		if(s[i]=='(') { //左括号直接入栈 
			fh.push(s[i]);
		}
		else if(s[i]==')') {
			if(fh.empty()) return false; //如果栈为空且当前为有括号,肯定不合法 
			if(!fh.empty()&&fh.top()=='(') fh.pop(); //如果栈顶为左括号,则一一对应,直接弹出 
		}
	}
	if(fh.empty()) return true; //如果为空,说明合法;反之,不合法 
	else return false;
}

int main() {
	scanf("%d",&n);
	scanf("%s",op+1);
	for(register int i=2;i<=n;i++) { //因为只处理链的情况,所以父亲节点可以不管 
		scanf("%d",&x);
	}
	for(register int i=1;i<=n;i++) { //表示从根节点到第i号节点 
		for(register int l=1;l<i;l++) { //枚举字符串 
			ops=op[l]; //注意初始化 
			if(op[l]==')') continue; //一开始就是左括号,肯定不合法 
			for(register int r=l+1;r<=i;r++) {
				ops+=op[r];
				if(ops.length()%2!=0) continue; //长度为奇数,肯定不合法 
				if(check(ops)==true) sum[i]++; //sum[i]存储到i号节点有多少个合法字符串 
			}
		}
	}
	for(register int i=1;i<=n;i++) { //根据题目要求求出答案 
		ans=ans xor (sum[i]*i);
	}
	printf("%d",ans);
	return 0;
}
  1. 上面的代码的问题就在于枚举每一条路上的所有字符串太浪费时间,所以我们需要找递推式,使得计算每个点以前的合法字符串更简便,先来手推:
我们设每个节点及以前的单个合法字符串'()'为now
再设全部合法字符串'(())'或'()()'为sum

例1:
字符串:()()
节点:1 2 3 4
每个节点的now:0 1 0 2
每个节点的sum:0 1 1 3

例2:
字符串:()(())
节点:1 2 3 4 5 6
now:0 1 0 0 1 2
sum:0 1 1 1 2 4

我们发现:一个后括号如果能匹配一个前括号,假设这个前括号的前1位同样有一个已经匹配了的后括号,那么我们可以把当前的匹配和之前的匹配序列合并,当前now,其实就等于前面那个后括号的now+1

计算出了now,那sum就很容易得出:sum[i]=sum[i-1]+now[i]

/* 跑过链的全部分:1-7点和11-14点 , 55pts*/

#include <bits/stdc++.h>
using namespace std;
int n,x;
long long ans,sum[510005],now[510005]; //注意开long long 
char op[510005];
stack<int> fh; //注意一下现在栈的类型是int(因为存的下标) 

int main() {
	scanf("%d",&n);
	scanf("%s",op+1);
	for(register int i=2;i<=n;i++) { //依旧没有用 
		scanf("%d",&x);
	}
	for(register int i=1;i<=n;i++) {
		if(op[i]==')') {
			if(!fh.empty()) { 
				int t=fh.top(); //如果栈不为空则满足now的条件,找到最近的合法括号下标 
				fh.pop();
				now[i]=now[t-1]+1; //核心递推式1 
			}
		}
		else if(op[i]=='(') fh.push(i); //存放的是下标 
		sum[i]=sum[i-1]+now[i]; //核心递推式2 
	}
	for(register int i=1;i<=n;i++) {
		ans=ans xor (sum[i]*i);
	}
	printf("%lld",ans);
	return 0;
}

解决了链的所有分,我们尝试解决树的情况,因为树可以看做很多条链组成,所以我们在链的核心思路上进行进一步思考

  1. 链的父亲就是当前节点i的前一个,但是在树上父亲编号不一定就是i-1,所以我们要想办法转换求now和sum值的递推式

解决:now是从上一个合法括号递推而来,在链中是now[t-1],在树中就是t的父亲节点now[fa[t]];sum同理,将sum[i-1]改成sum[fa[i]]即可

  1. 链直接遍历1~n即可,但是树的遍历是从父亲到儿子的递归,所以我们要知道怎么遍历

解决:记录每个点的父亲节点,再建图

  1. 因为在树上,每个父亲可能有多个儿子,那么就会涉及到每一次dfs后栈的状态问题,所以我们还需要回溯栈的状态

解决:如果当前括号为右括号,则回溯时需要重新压入;如果是左括号,则弹出

/* 满分Code */

#include <bits/stdc++.h>
using namespace std;
stack<int> fh; 
char op[510005];
int n,x,tot,head[510005];
long long ans,fa[510005],sum[510005],now[510005];

struct node {
	int to,net;
} a[510005];

inline void add(int u,int v) {
	a[++tot].to=v;
	a[tot].net=head[u];
	head[u]=tot;
}

inline void dfs(int x) {
	int t=0;
	if(op[x]==')') {
		if(!fh.empty()) {
			t=fh.top();
			fh.pop();
			now[x]=now[fa[t]]+1;
		}
	}
	else if(op[x]=='(') fh.push(x);
	sum[x]=sum[fa[x]]+now[x];
	for(register int i=head[x];i;i=a[i].net) { //邻接表存图遍历 
		int v=a[i].to;
		dfs(v);
	}
	if(t!=0) fh.push(t); //注意不能直接判断栈是否为空来压入 
	else if(op[x]=='(') fh.pop();
}

int main() {
	scanf("%d",&n);
	scanf("%s",op+1);
	for(register int i=2;i<=n;i++) {
		scanf("%d",&x);
		fa[i]=x; //记录父亲节点 
		add(x,i); //建边 
	}
	dfs(1);
	for(register int i=1;i<=n;i++) {
		ans=ans xor (sum[i]*(long long)i);
	}
	printf("%lld",ans);
	return 0;
}

后序:

啊...终于解决了这道题,来总结一下:

  1. 敲代码前一定要确保读懂题意,否则会错得不知所以然

  2. 不要上来就想满分思路(当然你强就想怎么就怎么,蒟蒻在线卑微QAQ),分析数据范围和数据特性,从简单的情况开始做

  3. 一定要多手推,不要怕麻烦,说不定推着推着思路就出来了

本题的解决过程:暴力->链->树(正解)


写给自己:

去年考试简直是惨不忍睹...

对我本身算是一种毁灭性的打击吧,学OI的兴趣和信心一下子就全部丧失完了,且赛后一直处于滑铁卢的阴霾中,真的挺难受的

在多方开导下慢慢走出来,继续OI之路,但是内心还是一直在回避考试、害怕失败

嗯...希望自己能坚定OI的心,不管学的好还不好、不管别人怎么说,沿着自己的路一步一步地前进,努力终不会白费的


posted @ 2020-06-17 11:06  Eleven谦  阅读(400)  评论(0编辑  收藏  举报