Luogu P5658 括号树

Luogu P5658 括号树

来补一道当年考场上没做出来的题。

不难想到树上 DP,关键在于如何设置函数与转移。

按题意,记 $k_i$ 为 以 $s_i$ 结尾的串中的合法子串数;记 $cnt_i$ 为 以 $s_i$ 结尾的合法子串数。请注意这两者的差别,这样做的动机会在转移方程处进行说明。

现在考虑转移,我们首先需要考虑当前节点的括号匹配情况,这可以用一个全局栈来维护。具体的实现方式是:

  • 若当前节点为左括号,则将当前节点的编号推进栈中;
  • 若当前节点为右括号,且栈为空,则说明此右括号无法匹配,不需要对栈操作;
  • 若当前节点为右括号,且栈不空,则说明此右括号可以匹配,匹配到的左括号的节点编号即为栈顶记录的编号,将栈顶元素弹出。

那么基于当前节点(记为节点 $x$)的括号匹配情况,我们得到以下转移方程:

  • 若当前节点为左括号,则 $cnt_x=0, k_x=k_{fa(x)}$;
  • 若当前节点为右括号,且栈为空,则 $cnt_x=0, k_x=k_{fa(x)}$;
  • 若当前节点为右括号,且栈不空,则记匹配到的左括号的节点编号为 $y$,有 $cnt_x=cnt_{fa(y)}+1, k_x=k_{fa(x)}+cnt_x$。

先解释 $cnt_x$ 的转移,注意到 $cnt_x$ 只在第三种情况下会发生转移:当 $s_x$ 与 $s_y$ 发生匹配后,考虑到 $cnt_x$ 的含义,则以 $s_i$ 结尾的合法子串有 $\overline{s_j \cdots s_i}$ 这一个以 $s_{fa(y)}$ 结尾的合法子串接上 $\overline{s_j \cdots s_i}$ 共 $cnt_{fa(y)}$ 个

这也就说明了为什么要将 $k_{fa(y)}$ 与 $cnt_{fa(y)}$ 分开记录。因为 $k_{fa(y)}$ 表示的是以 $s_{fa(y)}$ 结尾的串中的合法子串数,若是这些合法子串不以 $s_{fa(y)}$ 结尾,则不能与之后的 $\overline{s_j \cdots s_i}$ 连接起来作为一个新的合法子串。

而 $k_x$ 的转移则不难理解。若 $s_x$ 不发生匹配,则 $k_x$ 继承 $k_{fa(x)}$ 的值;否则就在继承 $k_{fa(x)}$ 的值的基础上再加上新增的以 $s_x$ 结尾的合法子串数,即 $cnt_x$。

代码实现上,利用 DFS 即可。要注意搜索的回溯。

#include<bits/stdc++.h>
#define N 500010

int n;
long long ans;
char str[N];

std::vector <int> v[N];
std::stack <int> s;

struct node {
	int fa;
	long long k,cnt;
	char c;
};

node a[N];

namespace WalkerV {
	void Read() {
		scanf("%d%s",&n,str+1);
		for(int i=1;i<=n;i++) {
			a[i].c=str[i];
		}
		for(int i=2;i<=n;i++) {
			scanf("%d",&a[i].fa);
		}
		return;
	}
	
	void DFS(int x) {
		if(a[x].c=='(') {
			a[x].k=a[a[x].fa].k;
			s.push(x);
			for(int i=0;i<v[x].size();i++) {
				DFS(v[x][i]);
			}
			s.pop();
		}
		else if(a[x].c==')') {
			if(s.empty()) {
				a[x].k=a[a[x].fa].k;
				for(int i=0;i<v[x].size();i++) {
					DFS(v[x][i]);
				}
			}
			else {
				int y=s.top();
				s.pop();
				a[x].cnt=a[a[y].fa].cnt+1,a[x].k=a[x].cnt+a[a[x].fa].k;
				for(int i=0;i<v[x].size();i++) {
					DFS(v[x][i]);
				}
				s.push(y);
			}
		}
		return;
	}
	
	
	void Solve() {
		for(int i=1;i<=n;i++) {
			v[a[i].fa].push_back(i);
		}
		DFS(1);
		for(int i=1;i<=n;i++) {
			ans^=(long long)i*a[i].k;
		}
		return;
	}
	
	void Print() {
		printf("%lld\n",ans);
		return;
	}
}

int main()
{
	WalkerV::Read();
	WalkerV::Solve();
	WalkerV::Print();
	return 0;
}
posted @ 2022-10-27 16:27  WalkerV  阅读(46)  评论(0编辑  收藏  举报