【CSP2019】括号树

栈,DP

题目大意:

给定一棵树,每个结点为左括号或者右括号。

一个结点代表的串为从根到该结点的所有括号依次排列形成。

对每个结点,求其代表的串中,有多少子串的括号完全匹配。

题解:

考虑令 $f_i$ 表示以 $i$ 结尾的合法子串的个数。显然 $f_i\neq 0$ 必须满足 $i$ 位置是右括号。

由于以 $i$ 结尾的串唯一确定,可以找到一个唯一确定的位置 $x$ 满足 $x$ 左括号,且 $x$ 和 $i$ 中间的串是完全匹配的。

那么从 $x$ 到 $i$ 的这段是完全匹配的,并且是以 $i$ 结尾的完全匹配的串中最短的。

其他以 $i$ 结尾的匹配的串,必定是之前的一段匹配的串,和 $x$ 到 $i$ 这段拼接而成的。这些串的个数为 $f_{fa[x]}$。

那么显然的转移为 $f_i=f_{fa[x]}+1$。

我们只要知道一个点对应的 $x$ 就好了。

这个很容易用栈维护。在遍历树的时候,左括号扔栈里,遇到右括号,则栈顶的左括号就是它所对应的 $x$,然后把这个括号弹出即可。

遍历时需要涉及回滚操作。由于每个结点对栈的修改是 $O(1)$ 的,所以记录一下这层干了什么,就可以方便地回滚。

最后对 $f$ 做一遍到根路径的前缀和即可。

时间复杂度 $O(n)$。

注意没得匹配的右括号的情况。

Code:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N=5e5+6;
int n,head[N],cnt,fa[N];
LL f[N];
char s[N];
struct edge{
    int to,nxt;
}e[N];
int sta[N],top=0;
void dfs(int now){
    int bak=0;
    if(s[now]=='(')sta[++top]=now;else
    if(top&&s[sta[top]]=='('){
        bak=sta[top];
        f[now]=1+f[fa[bak]];
        --top;
    }else sta[++top]=now;
    for(int i=head[now];i;i=e[i].nxt)
    dfs(e[i].to);
    if(bak)sta[++top]=bak;else --top;
}
void dfs2(int now){
    for(int i=head[now];i;i=e[i].nxt)f[e[i].to]+=f[now],dfs2(e[i].to);
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>(s+1);
    for(int i=2;i<=n;++i){
        cin>>fa[i];
        e[++cnt]=(edge){i,head[fa[i]]},head[fa[i]]=cnt;
    }
    dfs(1);dfs2(1);
    LL ans=0;
    for(int i=1;i<=n;++i)
    ans^=i*f[i];
    cout<<ans<<'\n';
    return 0;
}