传送门 to luogu
传送门 to nowcoder
对于子串,完全可以这么定义:
首先定义一个函数 f ( s ) = a 1 a 2 … a n − 1 f(s)=a_1a_2\dots a_{n-1} f(s)=a1a2…an−1 (即字符串 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} l≤px∧[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.并不是写炸了,而是真的想错了……大样例能过你敢信?
他识趣地离开了。