洛谷 P5658 括号树(NOIp 提高组2019)

题目传送门

首先为切掉去年\(T2\)小小感动一下。毕竟去年要是能切掉就能省一了。

思路

一开始我做这个题其实根本就不是奔着正解去的,而是想拿特殊性质的部分分。由于之前做过一道括号匹配方案的题,那个题是道\(dp\),所以我就自然而然地想到了\(dp\)(其实不是\(dp\),就是个递推吧)。特殊性质的含义其实就是:给你一个括号序列,让你判断合法字串的个数。由于中间每一个点都要求,也可以想到递推。那么我们考虑新加入的括号分类讨论:如果这是一个左括号,最好办了,合法子串个数直接复制前面的即可;但如果这是一个右括号,那就又有两种情况:它前面的括号是一个左括号,那么直接合法子串+1。但还要注意的是,如果这个左括号之前也是一个合法的子串,那么这个新组成的合法子串可以和之前连续的合法子串继续贡献。比如从这个左括号之前有3个连续的合法子串,那么就可以贡献3(长度分别为4,3,2,长度为1就相当于自己,刚刚已经加过了);如果它之前是一个右括号,那么又要分类讨论一次:如果这个右括号可以和之前的左括号组成合法的括号,那么我们就要找到那个左括号,并看做成上一种情况,找左括号之前连续的合法子串然后加和。这里注意到一点,一个左括号和一个右括号的合法匹配是唯一的。这个性质不必严谨证明,感性理解一下即可。比如一个左括号和右括号中间夹了一些合法括号序列,那么如果你将右括号向左移,中间显然会有左括号失去它的右括号从而无法匹配,右移也是同理,所以我们只需要找到那个合法的就一定是唯一的。

做完了

竟没有想象中那么难,通过部分分想到了正解

其实正解跟部分分就只不过是从后往前循环改成了从树上一步步往上跳而已。(还减小了时间复杂度。。。)

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long int ll;
ll n,fa[500005],ans; 
char c[500005];
struct F{
	ll tot,sum,suit;//sum有三个答案:-1,0,1,分别代表左括号,能与前面左括号匹配的右括号,不能与前面左括号匹配的右括号;tot代表合法子串数量;而suit代表从这个节点之前连续的合法字串的个数
}f[500005];
int main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++){
		cin>>c[i];
	}
	for(ll i=2;i<=n;i++){
		scanf("%lld",&fa[i]);
	}
//	for(ll i=1;i<=n;i++){
//		printf("第i号节点的父节点是%lld\n",fa[i]);
//	}
//	printf("\n");
	f[1].tot=0;
	if(c[1]=='(') f[1].sum=-1;
	else f[1].sum=1;//初始化
	for(ll i=2;i<=n;i++){
		if(c[i]=='('){
			f[i].sum=-1;
			f[i].tot=f[fa[i]].tot;//新加入的左括号不可能匹配,所以直接复制
			if(c[fa[i]]==')'&&f[fa[i]].sum==0)//如果这个左括号之前有合法子串,那么就过来
			f[i].suit=f[fa[i]].suit;
			else f[i].suit=0;//如果它前面没有合法子串的话,要注意将它清0
		}
		else{//右括号 
			if(f[fa[i]].sum==-1){//右括号与左括号刚好匹配
				f[i].tot=f[fa[i]].suit+f[fa[i]].tot+1;//思路中的公式
				f[i].suit=f[fa[i]].suit+1;//连续合法子串个数+1
				f[i].sum=0;
			}
			if(f[fa[i]].sum==1){//前面的右括号已经不能匹配了,再来一个右括号显然不能匹配
				f[i].tot=f[fa[i]].tot;
				f[i].suit=0;//别忘清0
				f[i].sum=1;
			}
			if(f[fa[i]].sum==0){
				ll p=1,j=fa[i];
				while(j!=0){
					if(c[j]=='(') p--;//栈的思想找合法括号匹配
					else p++;
					if(p==0){
						f[i].tot=f[fa[i]].tot+f[j].suit+1;
						f[i].suit=f[j].suit+1;
						f[i].sum=0;//找到的话就更新
						break;
					}
					j=fa[j];
				}
				if(f[i].tot==0&&f[i].sum==0){//注意到如果更新了的话tot值≥1,所以如果等于0显然是没有被更新,也就是说明这个右括号不能找到匹配的左括号
					f[i].tot=f[fa[i]].tot;
					f[i].sum=1;//suit还是0没必要写
				}
			}
		}
	}
//	for(ll i=1;i<=n;i++){
//		printf("从根结点到第%lld号节点组成的序列合法括号子串个数为%lld\n",i,f[i].tot);
//	}
	for(ll i=2;i<=n;i++){
		ans = ans xor (i*f[i].tot);
	}
	printf("%lld\n",ans);
	return 0;
}

不知道要不要开\(longlong\),被卡怕了而已。。。

posted @ 2020-09-12 23:27  徐明拯  阅读(136)  评论(0编辑  收藏  举报