[2019 CSP-S Day1 T2]括号树

题目

传送门 to luogu

传送门 to nowcoder

思路

对于子串,完全可以这么定义:

首先定义一个函数 f ( s ) = a 1 a 2 … a n − 1 f(s)=a_1a_2\dots a_{n-1} f(s)=a1a2an1 (即字符串 s s s 去掉末尾字符),我们说一个串 s 0 s_0 s0 s s s 的子串,当且仅当 s 0 s_0 s0 f ( s ) f(s) f(s) 的子串,或者 s 0 s_0 s0 s s s 的后缀。

更通俗的说,子串要么包括最后一个字符,要么不包括这不是废话吗

但是!本题中某个节点对应的字符串,去掉末尾字符,恰好就是父节点的字符串!

这提示我们,只计算以 x x x 作为最后一个字符的子串的数量 b x b_x bx 。那么 k x = k f a ( x ) + b x k_x=k_{fa(x)}+b_x kx=kfa(x)+bx

合法的括号序列,一定以右括号)结尾。所以只在为右括号的节点上计算 b x b_x bx

我们得先找到这个节点与哪个左括号(匹配。这本来是交给栈来做的。但是……

回溯怎么维护栈?——你的子节点一直弹栈,把你爹 祖坟 给挖了。

u p d a t e : update: update: 大佬让我打脸了…… 回溯可以维护栈 。

其实,我们不需要真的把栈存下来。我们用栈的目的,是求当前括号与谁匹配。

为了实现这一目的,用 f ( x ) f(x) f(x) 表示 x x x 的祖先(包括 x x x )中最下面的一个、未被匹配的(

或者说,假设递归到 x x x 的时候维护了一个栈,栈顶的左括号是谁。

那么 x x x 就和 p x = f [ f a ( x ) ] p_x=f[fa(x)] px=f[fa(x)] 匹配(当 x x x 是右括号时)。

一定有递推式

f ( x ) = { x ( x = L E F T ) f ( f a ( p x ) ) ( x = R I G H T ) f(x)= \begin{cases}x & (x=LEFT)\\ f(fa(p_x)) & (x=RIGHT)\end{cases} f(x)={xf(fa(px))(x=LEFT)(x=RIGHT)

x x x 是右括号时, x x x p x p_x px 之间的所有左括号都已经被匹配了。更前面一个的左括号,一定在 p x p_x px 的祖先中(不包括 p x p_x px 本身)。

x x x p x p_x px 一定不可以只选择一部分,并且自成体系,所以 [ l , x ] [l,x] [l,x] 为合法的括号序列,当且仅当 l ≤ p x ∧ [ l , father ( p x ) ] ∈ A N S l\le p_x\wedge[l,\text{father}(p_x)]\in \Bbb{ANS} lpx[l,father(px)]ANS A N S \Bbb{ANS} ANS 表示合法括号序列,包含空集。

wow \text{wow} wow,合法的 l l l 的数量不就是 b f a ( p x ) + 1 b_{fa(p_x)}+1 bfa(px)+1 嘛( 1 1 1 来自于 l = p x l=p_x l=px 的情况)!

所以 b x = b father ( p x ) + 1 b_x=b_{\text{father}(p_x)}+1 bx=bfather(px)+1 。然后没了。

代码

其实不需要把 k x k_x kx 数组开出来,直接在 dfs \text{dfs} dfs 中下传即可。

#include 
#include 
#include 
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -1;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}

struct Edge{
	int to, nxt;
	Edge(){	}
	Edge(int T,int N):to(T),nxt(N){	}
};

const int MaxN = 500005;
int n, head[MaxN], val[MaxN]; Edge edge[MaxN];
void addEdge(int from,int to){
	static int cntEdge = 0;
	edge[cntEdge] = Edge(to,head[from]);
	head[from] = cntEdge ++;
}
void input(){
	n = readint();
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	for(int i=1; i<=n; ++i)
		val[i] = (getchar() == '(' ? 1 : -1);
	for(int i=2; i<=n; ++i)
		addEdge(readint(),i);
}

long long ans;
int last[MaxN], cnt[MaxN], father[MaxN];
void dfs(int x,long long now){
// cnt[x]: b_x  last[x]: f(x)  now: s_{father(x)}
	if(val[x] == 1)
		last[x] = x;
	if(val[x] == -1){
		int id = last[father[x]]; // p_x
		last[x] = last[father[id]];
		if(id) // 可以匹配 
			cnt[x] = cnt[father[id]]+1;
	}
	now += cnt[x];
	ans = ans^(now*x);
	for(int i=head[x]; ~i; i=edge[i].nxt){
		father[edge[i].to] = x;
		dfs(edge[i].to,now);
	}
}
void solve(){
	dfs(1,0);
	writeint(ans);
	putchar('\n');
}

int main(){
	// freopen("brackets.in","r",stdin);
	// freopen("brackets.out","w",stdout);
	input();
	solve();
	return 0;
} 

后记

SXY \text{SXY} SXY大佬说:“为什么要写这种垃圾题?”

我的眼里冒出怒火,“有个弱者把这个‘垃圾题’从 100pts \text{100pts} 100pts写成了 10pts \text{10pts} 10pts……”

p.s. \text{p.s.} p.s.并不是写炸了,而是真的想错了……大样例能过你敢信?

他识趣地离开了。

你可能感兴趣的:(C++,动态规划,字符串)