首页 > 其他分享 >Luogu P5658 括号树

Luogu P5658 括号树

时间:2022-10-27 16:38:17浏览次数:97  
标签:子串 cnt 结尾 括号 Luogu P5658 fa 节点

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

标签:子串,cnt,结尾,括号,Luogu,P5658,fa,节点
From: https://www.cnblogs.com/luoshui-tianyi/p/16832723.html

相关文章

  • 栈的使用以及括号匹配扩展
    678. ValidParenthesisStringMedium68824FavoriteShareGivenastringcontainingonlythreetypesofcharacters:'(',')'and'*',writeafunctiontocheckwhet......
  • 括号匹配
     合法的括号匹配序列被定义为:1.空串""是合法的括号序列2.如果"X"和"Y"是合法的序列,那么"XY"也是一个合法的括号序列3.如果"X"是一个合法的序列,那么"[X]"也是一个......
  • 计算器(带括号)
    #include<iostream>//forcoutendl#include<stack>//forstack#include<string>//forstring#include<vector>//forvector#include<algorithm>//forpow()usingname......
  • 括号配对问题
    描述现在,有一行括号序列,请你检查这行括号是否配对。第一行输入一个数N(0输出每组输入数据的输出占一行,如果该字符串中所含的括号......
  • CSPS2019 括号树 题解
    链的部分分我们设f[i]表示以i结尾的括号序列有多少个,那么i的实际答案就是f的前缀和显然,所有左括号和不能匹配的右括号的f均为0对于每一个能匹配的右括号i,我们找到与之......
  • 【luogu P6130】随机红包(数学)(期望)
    随机红包题目链接:luoguP6130题目大意把一个数1分成n份,求第k小的期望大小,多次询问。思路首先考虑最小的期望大小,那假设最小的是\(x\),剩下的都大于\(x\)。那......
  • Luogu 2894 酒店Hotel
    题目链接:​​传送门​​题目描述:参考样例,第一行输入n(1≤n≤50,000),m(1≤M<50,000),n代表有n个房间,编号为1—n,开始都为空房,m表示以下有m行操作,以下每行先输入一个......
  • Luogu 3478 [POI2008]STA-Station
    题目链接:​​传送门​​题目描述给出一个N个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大输入样例814564567682434输出样例7一句话题意好......
  • Luogu 1507 NASA的食物计划
    题目链接:​​传送门​​题目背景NASA(美国航空航天局)因为航天飞机的隔热瓦等其他安全技术问题一直大伤脑筋,因此在各方压力下终止了航天飞机的历史,但是此类事情会不会在以......
  • Luogu 1853 投资的最大效益
    题目链接:​​传送门​​题目背景约翰先生获得了一大笔遗产,他暂时还用不上这一笔钱,他决定进行投资以获得更大的效益。银行工作人员向他提供了多种债券,每一种债券都能在固定的......