说在前面的话
谨以此篇题解,纪念我初中的两年\(OI\)生涯以及不长不短的停课时光。
但愿高中还能够继续学习\(OI\)吧,也衷心希望其他\(OIer\)不要再犯类似我的错误。
题意
给定一棵有\(n\)个节点的树,每个节点对应一个括号(左或右),需要求出从该节点到根节点的简单路径所构成的括号串中合法括号子串的数量。
其中合法括号串的定义是这样的
()
是合法括号串。- 如果
A
是合法括号串,则(A)
是合法括号串。 - 如果
A
,B
是合法括号串,则AB
是合法括号串。
其中\(n\le 5\times 10^5\)
题解
1. 初步思路
令\(cnt_i\)表示从\(i\)节点到根节点所构成的括号串中合法括号子串的数量,\(f\)表示\(i\)节点的父亲,则有一个很显然的结论:
\[ cnt_i=cnt_f+(\text{以i节点为结尾的合法括号子串的个数}) \]
这个结论应该无需证明了……这是解题的关键,于是我们就只需要考虑以\(i\)节点为结尾的合法括号子串的个数了(设其为\(t_i\))。
2. 统计答案
先考虑怎么暴力统计,显然直接从\(i\)节点往上跳,统计合法括号子串数即可,这样做是\(O(n_2)\)的。
再考虑给出的合法括号串的定义:“如果A
,B
是合法括号串,则AB
是合法括号串”。
举个例子,考虑暴力的过程,如果以\(i\)为结尾的括号串是()()()()
,我们总共了统计了\(4\)个,但其实,我们只需要统计最右边(dfs序最大)的那一对合法的括号,而左边的三对括号就相当于\(t_{f_f}\)(即以\(f_f\)为结尾的合法括号串的数量),这是因为最左边的括号串()
合法,右边的三个括号串()
,()()
,()()()
合法,联系上面的性质,所以将它们连起来同样合法。
到这一步之后,貌似大多数人都用栈来做,我在考场上\(yy\)出了一种奇♂妙的方法,在这里分享一下。
于是我们设\(re_i\)为满足从\(i\)节点到该节点所构成的括号串为合法括号串,且深度最大的节点,得到\(cnt_i\)的表达式
\[ cnt_i=cnt_f+t_{f_{re_i}}+1 \]
结合一下先前的例子()()()()
,\(cnt_f\)不用多讲,\(1\)表示的是以\(re_i\)为开头以\(i\)为结尾的合法括号串(即例子中最左边的的()
),\(t_{f_{re_i}}\)就是例子中右边的三个合法括号串()
,()()
,()()()
,至于这三个括号串为什么要记入答案上文已讨论。
再结合一下代码看看:
cnt[x]+=cnt[f],fa[x]=f;
if (a[x]==1) // 只有a[x]==1(即括号为')')才有可能存在以x为结尾的合法括号串
{
while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]]; // 找到re[x]
if (f==0||a[f]==1) re[x]=-1; // re[x]需合法
else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1; // 统计答案。cnt[fa[f]]-cnt[fa[fa[f]]]就等于上文中的t[fa[f]]
}
else re[x]=-1;
经验教训
笔者在考场做这道题时,将上文中的while
打成了if
,于是(洛谷自测)\(100 \to 10\)
其实是没有考虑到这种情况((())())
。(至于为什么错可以手玩一下)
然后就开开心心\(\text{AFO}\)搞文化课了
这里以亲身教训提醒大家,一定要注意细节!考虑情况一定要充分!不要重蹈我的覆辙!
祝大家人人取得满意的成绩(我是拿不到了)
代码
其实代码就很短啦,\(qwq\)。
#include <stdio.h>
using namespace std;
template <typename T> inline void Read(T &t)
{
int c=getchar(),f=0;
for (;c<'0'||c>'9';c=getchar()) f=(c=='-');
for (t=0;c>='0'&&c<='9';c=getchar()) t=(t<<3)+(t<<1)+(c^48);
if (f) t=-t;
}
typedef long long ll;
const int N=5e5+5;
int n,tot,head[N],a[N],fa[N],re[N];
ll ans,cnt[N];
char temp[N];
struct Edge
{
int to,next;
void add(int x, int y) { to=y,next=head[x],head[x]=tot; }
} e[N<<1];
void dfs(int x, int f)
{
cnt[x]+=cnt[f],fa[x]=f;
if (a[x]==1)
{
while (a[f]!=-1&&re[f]!=-1) f=fa[re[f]];
if (f==0||a[f]==1) re[x]=-1;
else re[x]=f,cnt[x]+=cnt[fa[f]]-cnt[fa[fa[f]]]+1;
}
else re[x]=-1;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
dfs(v,x);
}
}
signed main()
{
Read(n);
scanf("%s",temp);
for (int i=1;i<=n;i++)
a[i]=(temp[i-1]=='('?-1:1);
for (int i=2,f;i<=n;i++) Read(f),e[++tot].add(f,i);
re[0]=-1;
dfs(1,0);
for (ll i=1;i<=n;i++) ans^=(cnt[i]*i);
printf("%lld\n",ans);
return 0;
}
结语
笔者大概率是\(\text{AFO}\)了,但还是希望这篇题解能给做出或没有做出这道题的人带来一些帮助,也算是我\(OI\)生涯的回光返照吧
去搞文化课准备中考了。
原文地址:https://www.cnblogs.com/asd369-blog/p/CSP-S2-2019-brackets.html