前言
感觉提高不是很稳啊。
这道题也就勉强算在承受范围内吧。考场应该是写出了正解的。
洛谷自测上是满分。希望不要出什么差错吧。。。
题目:
题目链接:https://www.luogu.org/problem/P5658?contestId=24103
本题中合法括号串的定义如下:
()
是合法括号串。- 如果
A
是合法括号串,则(A)
是合法括号串。 - 如果
A
,B
是合法括号串,则AB
是合法括号串。
本题中子串与不同的子串的定义如下:
4. 字符串 S
的子串是 S
中连续的任意个字符组成的字符串。S
的子串可用起始位置
l
l
l 与终止位置
r
r
r 来表示,记为
S
(
l
,
r
)
S (l, r)
S(l,r)(
1
≤
l
≤
r
≤
∣
S
∣
1 \leq l \leq r \leq |S |
1≤l≤r≤∣S∣,
∣
S
∣
|S |
∣S∣ 表示 S 的长度)。
5. S
的两个子串视作不同当且仅当它们在 S
中的位置不同,即
l
l
l 不同或
r
r
r 不同。
一个大小为 n n n 的树包含 n n n 个结点和 n − 1 n − 1 n−1 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达。
小 Q 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 n n n 的树,树上结点从 1 1 1 ∼ n n n 编号, 1 1 1 号结点为树的根。除 1 1 1 号结点外,每个结点有一个父亲结点, u u u( 2 ≤ u ≤ n 2 \leq u \leq n 2≤u≤n)号结点的父亲为 f u f_u fu( 1 ≤ f u < u 1 ≤ f_u < u 1≤fu<u)号结点。
小 Q 发现这个树的每个结点上恰有一个括号,可能是(
或)
。小 Q 定义
s
i
s_i
si 为:将根结点到
i
i
i 号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串。
显然 s i s_i si 是个括号串,但不一定是合法括号串,因此现在小 Q 想对所有的 i i i( 1 ≤ i ≤ n 1\leq i\leq n 1≤i≤n)求出, s i s_i si 中有多少个互不相同的子串是合法括号串。
这个问题难倒了小 Q,他只好向你求助。设
s
i
s_i
si 共有
k
i
k_i
ki 个不同子串是合法括号串, 你只需要告诉小 Q 所有
i
×
k
i
i \times k_i
i×ki 的异或和,即:
(
1
×
k
1
)
xor
(
2
×
k
2
)
xor
(
3
×
k
3
)
xor
⋯
xor
(
n
×
k
n
)
(1 \times k_1)\ \text{xor}\ (2 \times k_2)\ \text{xor}\ (3 \times k_3)\ \text{xor}\ \cdots\ \text{xor}\ (n \times k_n)
(1×k1) xor (2×k2) xor (3×k3) xor ⋯ xor (n×kn)
其中
x
o
r
xor
xor 是位异或运算。
思路:
我们设 a n s [ x ] ans[x] ans[x]表示路径 ( 1 , x ) (1,x) (1,x)中构成的括号串,以 x x x节点为右端点的所有区间有多少个合法括号串。
- 那么如果
x
x
x位置为
(
,那么显然 a n s [ x ] = 0 ans[x]=0 ans[x]=0。 - 如果
x
x
x位置为
)
,设 c n t [ x ] [ 1 / 2 ] cnt[x][1/2] cnt[x][1/2]为路径 ( 1 , x ) (1,x) (1,x)中左括号和右括号的个数,那么一个 x x x节点的祖先 y y y可以对 a n s [ x ] ans[x] ans[x]做贡献,当且仅当满足一下两个条件:
( 1 ) c n t [ x ] [ 1 ] − c n t [ y ] [ 1 ] = c n t [ x ] [ 2 ] − c n t [ y ] [ 2 ] (1)\ cnt[x][1]-cnt[y][1]=cnt[x][2]-cnt[y][2] (1) cnt[x][1]−cnt[y][1]=cnt[x][2]−cnt[y][2]
( 2 ) ∀ p ∈ ( y , x ) (2)\ ∀p\in(y,x) (2) ∀p∈(y,x),满足 c n t [ p ] [ 2 ] ≥ c n t [ p ] [ 1 ] cnt[p][2]\geq cnt[p][1] cnt[p][2]≥cnt[p][1]
那么我们就可以在访问每一个节点时,依次枚举它的每一个祖先,如果满足
c
n
t
[
x
]
[
1
]
−
c
n
t
[
y
]
[
1
]
=
c
n
t
[
x
]
[
2
]
−
c
n
t
[
y
]
[
2
]
cnt[x][1]-cnt[y][1]=cnt[x][2]-cnt[y][2]
cnt[x][1]−cnt[y][1]=cnt[x][2]−cnt[y][2],那么
a
n
s
[
x
]
+
+
ans[x]++
ans[x]++。直到
c
n
t
[
y
]
[
2
]
<
c
n
t
[
y
]
[
1
]
cnt[y][2]< cnt[y][1]
cnt[y][2]<cnt[y][1]时停止枚举。
这样我们就得到了一个
O
(
n
2
)
O(n^2)
O(n2)的算法,获得了
50
p
t
s
50pts
50pts的好成绩。
我们发现,其实我们只关心在路径
(
1
,
x
)
(1,x)
(1,x)中,深度最大的不满足
c
n
t
[
p
]
[
2
]
≥
c
n
t
[
p
]
[
1
]
cnt[p][2]\geq cnt[p][1]
cnt[p][2]≥cnt[p][1]的节点
p
p
p是哪一个。这样所有在路径
(
s
o
n
[
p
]
,
x
)
(son[p],x)
(son[p],x)中满足条件
(
1
)
(1)
(1)的点都可以做贡献。
其实
(
1
)
(1)
(1)的条件可以转化为
c
n
t
[
x
]
[
1
]
−
c
n
t
[
x
]
[
2
]
=
c
n
t
[
y
]
[
1
]
−
c
n
t
[
y
]
[
2
]
cnt[x][1]-cnt[x][2]=cnt[y][1]-cnt[y][2]
cnt[x][1]−cnt[x][2]=cnt[y][1]−cnt[y][2]。所以我们可以用
p
o
s
[
s
]
[
t
o
t
]
pos[s][tot]
pos[s][tot]记录
c
n
t
[
y
]
[
1
]
−
c
n
t
[
y
]
[
2
]
=
s
cnt[y][1]-cnt[y][2]=s
cnt[y][1]−cnt[y][2]=s的每一个
x
x
x的祖先
y
y
y编号。这样如果
c
n
t
[
x
]
[
1
]
−
c
n
t
[
x
]
[
2
]
=
s
cnt[x][1]-cnt[x][2]=s
cnt[x][1]−cnt[x][2]=s,那么能对
x
x
x做贡献的点就都在
p
o
s
[
s
]
pos[s]
pos[s]中。
那么我们可以用一个栈来记录
c
n
t
[
p
]
[
2
]
<
c
n
t
[
p
]
[
1
]
cnt[p][2]<cnt[p][1]
cnt[p][2]<cnt[p][1]的所有
p
p
p。其中
p
p
p是
x
x
x的祖先。此时如果节点
x
x
x为(
,那么直接将
x
x
x扔进栈里。如果
x
x
x为)
,那么就弹出栈顶。
这样如果栈顶是
p
p
p,那么能对
x
x
x做贡献的就是同时在路径
(
s
o
n
[
p
]
,
x
)
(son[p],x)
(son[p],x)和
p
o
s
[
c
n
t
[
x
]
[
1
]
−
c
n
t
[
x
]
[
2
]
pos[cnt[x][1]-cnt[x][2]
pos[cnt[x][1]−cnt[x][2]的节点。
所以就可以二分出
a
n
s
[
x
]
ans[x]
ans[x]。
发现
p
o
s
pos
pos中最多只会有
n
n
n个元素,所以可以开一个
v
e
c
t
o
r
vector
vector。
求出
a
n
s
[
x
]
ans[x]
ans[x]后,路径
(
1
,
x
)
(1,x)
(1,x)的合法括号串个数就是
∑
y
y
是
x
的祖先
a
n
s
[
y
]
\sum^{y\texttt{是}x\texttt{的祖先}}_{y}ans[y]
∑yy是x的祖先ans[y]。做前缀和即可。
注意回溯时需要在栈中弹出
x
x
x。
时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
代码:
#include <stack>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=500010,Inf=1e9;
int n,tot,a[N],cnt[N][3],head[N];
ll ans[N],orz;
char ch;
vector<int> pos[N*2];
stack<int> del;
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
int binary(int x,int tp)
{
int l=0,r=pos[x].size(),mid,res;
// for (int i=l;i<r;i++) printf("%d ",pos[x][i]);putchar(10);
while (l<=r)
{
mid=(l+r)>>1;
if (pos[x][mid]>=tp) r=mid-1,res=mid;
else l=mid+1;
}
return res;
}
void dfs(int x,int fa)
{
cnt[x][1]=cnt[fa][1]; cnt[x][2]=cnt[fa][2];
cnt[x][a[x]]++;
int s=cnt[x][1]-cnt[x][2]+N,pp=-1;
if (a[x]==1) del.push(x);
else
{
if (del.size()>1)
{
pp=del.top();
del.pop();
}
int tp=del.top();
pos[s].push_back(Inf);
ans[x]=pos[s].size()-binary(s,tp)-1;
pos[s].pop_back();
}
ans[x]+=ans[fa];
orz^=1LL*x*ans[x];
pos[s].push_back(x);
for (int i=head[x];~i;i=e[i].next)
dfs(e[i].to,x);
pos[s].pop_back();
if (del.top()==x) del.pop();
if (pp!=-1) del.push(pp);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
//while (ch=getchar()) if (ch=='('||ch==')') break;
while (1)
{
ch=getchar();
if (ch=='('||ch==')') break;
}
if (ch=='(') a[i]=1;
else a[i]=2;
}
for (int i=2,x;i<=n;i++)
{
scanf("%d",&x);
add(x,i);
}
del.push(-1);pos[N].push_back(0);
dfs(1,0);
printf("%lld\n",orz);
// for (int i=1;i<=n;i++)
// printf("%lld ",ans[i]);
return 0;
}