Luogu P5658 括号树|搜索+递推
CSP2019 S组D1T2
题目大意:一棵树上每个节点有(或),根到每个节点的路径可形成一括号串,求根到每个节点形成的括号串的合法括号字串个数,取异或和。
解析:该题为CSP2019提高组容易拿分的一道。选择写链是个不错选择(然后我就这里打挂了 70pts),正解其实在考场上也能想出。
子串说明要连续,而我们接好一个括号后它显然可以接上前面连续的括号序列。
即:设一个节点\(x\)的答案为\(ans_x\),设此次接的左括号位置\(y\),则\(ans_x+=ans_y+1\)(因为自己单独也算一个)。而一个节点的父亲节点的答案,也会在该节点中原封不动的展现,故应加上节点的所有父亲节点之答案。
我们采用\(dfs\)算法遍历整棵树,遇到节点为左括号进栈,否则在栈中匹配一个还没有匹配的左括号,成功匹配,则按上面累加答案。
这里有一个问题,即从\(dfs\)退出时,我们在这里的东西需销毁,这同时牵涉到上面左括号的匹配。因此,这里采用\(vis\)来维护上述。若该节点为左括号,则结束搜索时将该节点退出栈。否则取消该节点匹配的左括号的已匹配状态,然后退出搜索。
具体解析见代码。
#include<bits/stdc++.h>
using namespace std;
int cc,to[600000],net[600000],fr[600000],fa[600000],zhan[600000],t,n,nt[600000];
long long s[600000];
char ch[600000];string st;bool vis[600000];
void addedge(int u,int v)
{
cc++;
to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void dfs1(int x)//匹配括号
{
if (ch[x]=='(')
{
zhan[++t]=x;
}//压栈
else
{
nt[x]=t;//备份,不与全局冲突,后边操作用
while (vis[zhan[nt[x]]]&&nt[x])
{
nt[x]--;
}
if (nt[x])
{
vis[zhan[nt[x]]]=true;
s[x]=s[x]+1+s[fa[zhan[nt[x]]]];
}//匹配左括号
}
for (int i=fr[x];i;i=net[i])
{
dfs1(to[i]);
}//往下继续找
if (ch[x]=='(')
t--;
else vis[zhan[nt[x]]]=false;//还原数组
}
void dfs2(int x)//起累加父亲节点答案作用
{
for (int i=fr[x];i;i=net[i])
{
s[to[i]]+=s[x];
dfs2(to[i]);
}
}
int main()
{
// freopen("brackets.in","r",stdin);
// freopen("brackets.out","w",stdout);
cin>>n;
cin>>st;
for (int i=1;i<=n;i++)
{
ch[i]=st[i-1];
}
bool lian=true;
for (int i=2;i<=n;i++)
{
cin>>fa[i];
if (fa[i]!=i-1) lian=false;
addedge(fa[i],i);
} //建树
{
dfs1(1);
dfs2(1);
}//到这里原始答案已全部求出
long long ans=s[1];
for (int i=2;i<=n;i++)
{
ans^=s[i]*i;
}
cout<<ans<<endl;
return 0;
}
附:链的部分分代码(其实与正解中统计答案相似)
if (lian)
{
for (int i=1;i<=n;i++)
{
if (ch[i]=='(')
{
zhan[++t]=i;
}
else
{
if (!t) continue;
s[i]=1+s[fa[zhan[t]]];
t--;
}
}
for (int i=1;i<=n;i++)
{
s[i]+=s[i-1];
}
}