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;
}

浙公网安备 33010602011771号