括号树's 题解

这是一道令我很揪心的D1T2

原因是我在考场上想出了正解却没调出来。。。
题目链接
题面让你求一棵树上能匹配的括号串的个数。
呃,前面的20分直接暴力过(在加上是链随便打一下就好了啦),我就不解释了(明明是没写然后又不想写)。

然后说一下由于考场上没调出来而交的55分代码吧。

首先先来看一下链的性质。
明显的一件事,形如 (   (   )   ) (\ (\ )\ ) ( ( ) ) 这样的链,如果要把外层的括号匹配上,显然前提是你在这里面的所有括号能恰好匹配完而且你全部选上了,那么其实就是说你可以把每一层的括号分开算,它们对答案的贡献是互不影响的,那么就可以每层分开算。
然后再看看形如 (   )   (   )   (   ) (\ )\ (\ )\ (\ ) ( ) ( ) ( ) 这样的链,它对答案的贡献为 3 + 2 + 1 3+2+1 3+2+1 个(选1对括号,2对括号,3对括号能对答案做出的贡献),所以相当于当前层中,你每多加上一对括号,就对答案产生包括当前括号在内的,当前层已经匹配上的括号对数。
然后如果你发现最外面的那一层如果扫到一个右括号没有左括号可以来和它匹配,那么就相当于把这个链分成了两个互不相干的链,那么接下来再重新开始统计匹配。
所以。。。
读入数据后,由于题目给的是保证它的父节点是比它编号小一的,那么就可以直接一个 O ( n ) O(n) O(n) 的for循环搜下去,用 k t kt kt 表示当前所在层数, k s ks ks 表示当前层已匹配的括号数,每当要传到下一层的时候,就把 k s ks ks 传到 c s [ k t ] cs[kt] cs[kt] 里保存,等退回来的时候再用。然后再用一个 t m p tmp tmp 记录目前的答案,再用最后要输出的 a n s ans ans 异或上当前的 t m p ∗ i tmp*i tmpi。最后输出即可。
主要代码:

for(int i=0;++i<=n;){
	if(a[i]=='('){
		cs[kt]=ks;
		ks=0;
		++kt;
	}
	else if(kt>0){
		ks=cs[kt-1]+1;
		cs[kt]=0;
		--kt;
	}
	else ks=kt=0;
	tmp+=ks;
	ans^=(tmp*i);
}
进正解

其实你想出了链的思路,那么你离正解也不远了。。。
显然我们可以用dfs把链扩展到树上,再改一改转移的东西,然后就可以过了。。。
正解代码(上文是考试时候本来想拿来对拍的程序,下文是后来重新敲的,变量名没对上勿喷):

#include 
using namespace std;
const int N=5e5+10;
int n,ans=0,top[N],f;
long long tot=0,sum[N<<1],num[N];
char a[N];
struct lol{int x,y;} e[N]; 
void freo(){
	freopen("brackets.in","r",stdin);
	freopen("brackets.out","w",stdout);
}
void ein(int x,int y){
	e[++ans]=(lol){top[x],y};
	top[x]=ans;
}
int dfs(int x,int fa,int cnt,long long tmp){
	int opt;
	a[x]==')'?++cnt:(--cnt,opt=sum[cnt],sum[cnt]=0,++sum[cnt+1]);
	num[x]=sum[cnt]+tmp;
	for(int i=top[x],y;i;y=e[i].y,y==fa?:dfs(y,x,cnt,num[x]),i=e[i].x);
	a[x]==')'?:(--sum[cnt+1],sum[cnt]=opt,++cnt);
}
void init(){
	scanf("%d%s",&n,a+1);
	for(int i=1;++i<=n;scanf("%d",&f),ein(f,i));
}
void work(){
	dfs(1,0,500000,0);
	for(int i=0;++i<=n;tot^=(num[i]*i));
}
void prin(){
	printf("%lld",tot);
}
int main(){
//	freo();
	init();
	work();
	prin();
	return 0;
}

你可能感兴趣的:(复赛,题解,树形dp)