「乱搞」括号树
一年前我唯唯诺诺,一年后我重拳出击
题目大意:
大氵题:给定一颗树,树上有括号,问从根节点到每个节点形成的括号序列中不同合法括号子串的总个数异或之和
解法:
先考虑\(55\)\(pts\)的链,即一个括号序列中不同合法括号子串的总个数
容易把匹配括号对分成两组:同级和不同级的
举例:
图中相同颜色表示同级,同级括号对之间可以相互形成合法括号子串
如图中,2-3 和 4-5 同级,那么 2-3 4-5 2-5 都是合法括号子串,即每新增一个合法括号对,新增加的合法括号子串为同级合法括号对个数
同时还会发现,上一个同级括号对的右括号为当前括号对左括号的前一个元素,因此可以另开一个数组维护到当前右括号的同级括号对个数,出栈时转移即可
反之,不同级括号之间只会新增一个合法括号子串
因此得到算法:用单调栈维护左右括号匹配情况,入栈的是左括号的下标,出栈时,直接加上同级括号对个数,当然还要加上前一个元素的贡献
\(55pts\)代码:
//a_i==1表示i元素是右括号,b_i为到i的同级括号对个数,f_i为到i的不同合法括号子串的个数
for(int i=1;i<=n;i++){
f[i]=f[i-1];
if(a[i]==0)sta[++top]=i;
else{
if(!top){continue;}
int p=sta[top--];
if(a[i-1]==0&&a[i-2]==0)b[i]=1;
else b[i]=b[p-1]+1;
f[i]+=b[i];
}
}
有了链的代码,直接把它粘贴进DFS中就能AC这道题,但是需要改一些细节
1.把根节点到每个点之间形成的括号序列按链处理,这样直接做(开\(n\)个栈,每次跑一遍)是\(O(n^2)\),减少时间开销直接回溯,应用父亲的括号序列转移即可
2.由于树上标号不连续,所以\(b_v\)在序列中的前一个的位置应该是\(b_{par_v}\)
3.回溯时注意栈顶元素可能会发生改变,需要开变量记当前栈顶以继续回溯
代码跟链的几乎一样
代码:
void DFS(int u,int fa){
for(int x=head[u];x;x=e[x].next){
int v=e[x].to;
f[v]=f[u];
int flag=0;int p;
if(a[v]==0)sta[++top]=v;
else{
if(top){
p=sta[top--];
if(a[u]==0&&a[fa]==0)b[v]=1;
else b[v]=b[par[p]]+1;
f[v]+=b[v];
flag=1;
}
}
DFS(v,u);
if(a[v]==0)top--;
else if(flag)sta[++top]=p;
}
}