CSP-S 2019 D1T2 括号树

CSP-S 2019 D1T2 括号树

 
 
详细题面戳此处
 
 


丢啊,打了个巨丢人小错误自测只有10分。凉凉。


看到数据范围得知时间复杂度只能是\(O(N)\)\(O(Nlog(N))\)。但很明显本题没有任何单调性,所以肯定是\(O(N)\)做法。
于是我们把从\(1\)\(K_i\)点上所有的括号拉♂直,并看作一个序列。我们想到,每一个\('('\)不可能在它的左边找到匹配,而每一个\(')'\)会匹配它左边第一个没有被匹配的\('('\)。所以我们记录一个\(Last\)数组,表示上一个没有被匹配的\('('\)的位置。
然后再看看我们得出的\(O(N)\)算法结论,再结合一下这个序列的特性(每一个子节点的值都与其父节点有关),很明显这就是个动归。
于是我们开一个\(dp[i]\),记录\(1\)号到\(i\)号点的括号序列中合法括号串的个数。
转移方程:
(\(Num\)指当前点上的括号,\(Sum\)指当前点之前有多少个连续的括号块,\(F\)指当前节点的父亲节点)

  • \(1.Num[i]='(':dp[i]=dp[F[i]],Last[i]=i;\)
  • 此时,若\(Num[F[i]]=')'\)(不管它是否有匹配)\(:Sum[i]=Sum[F[i]];\)
    接下来是恶心至极的数组套数组转移
  • \(2.Num[i]=')'\&\&Last[F[i]]!=0:dp[i]=dp[F[i]]+1+Sum[Last[F[i]]],Last[i]=Last[F[Last[F[i]]]]\),\(Sum[i]=Sum[Last[F[i]]]+1;\)

让我来稍微解释一下
首先,新组成的大括号块可以和它所配对的\('('\)前方的连续括号块组成\(1+Sum[K]\)个合法串。于是当前的\(dp\)值就出来了。同时,由于我们用掉了一个\('('\),所以需要更新一下\(Last\)值,把它设为匹配的\('('\)前的第一个没有被匹配的\('('\)(由于本人Last设的是当前点的上一个未匹配的点,所以转移方程长到爆裂)。这样的话\(Sum\)值的转移就很好理解了,与\(Last\)相似,把它设为把它设为匹配的\('('\)前的连续括号块(不管有没有)并喜\(+1\)
 
呼哧呼哧呼哧呼哧呼哧
 


KONO代码……
食堂泼辣酱,咋瓦鲁多!

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const ll maxn=500010;
ll n,sum[maxn];
vector<int>son[maxn];
int f[maxn];
int la[maxn];
bool num[maxn];
ll dp[maxn];
ll ans;
char tmp[maxn];
void Dp(ll now)
{
	dp[now]=dp[f[now]];
	if(num[now]){
		if(la[f[now]]){
			dp[now]+=1+sum[la[f[now]]];
			la[now]=la[f[la[f[now]]]];
			sum[now]=sum[la[f[now]]]+1;
		}
	}
	else{
		la[now]=now;
		if(num[f[now]]==1)sum[now]=sum[f[now]];
		else sum[now]=0;
	}
	ans=(ans^(dp[now]*now));
	int k=son[now].size();
	for(int i=0;i<k;i++)
	Dp(son[now][i]);
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	cin>>tmp;
	int i,j;
	for(i=1;i<=n;i++){
		num[i]=tmp[i-1]=='('?0:1;
	}
	for(i=2;i<=n;i++)
	{
		int a;
		cin>>a;
		f[i]=a;
		son[a].push_back(i);
	}
	Dp(1);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2019-11-21 16:37  国土战略局特工  阅读(271)  评论(0)    收藏  举报