目录
显示
题目链接
https://www.luogu.org/problem/P5658
题解
因为树上问题和链上问题类似,因此我们先只考虑链上问题。
链上问题最暴力的做法是:对每个终点,两重循环枚举子串,判断子串是否合法,从而把每个终点对应的答案算出来,时间复杂度 \(O(n^3)\)。
注意到如果一个右端点为 \(r\) 的子串合法,那么它会对终点为 \(r,r+1,\ldots,n\) 的答案都会产生贡献,从而我们可以省去枚举终点的过程,只枚举子串。设右端点为 \(i\) 的合法子串个数为 \(f_i\),则终点 \(i\) 的答案 \(g_i\) 显然为 \(f_i\) 的前缀和,即:
$$g_i=\sum_{j=1}^{i} f_j$$
这样我们就可以将时间复杂度优化到 \(O(n^2)\)。
接下来怎样进一步优化呢?我们考虑合法括号串的递归性质。
如果
A,B是合法括号串,则AB是合法括号串。
如果两个合法括号串相邻,我们可以将其合并为一个更长的合法括号串。当前右括号位置对答案的贡献,则等于之前右括号位置对答案的贡献+1。
判断合法括号串可以用栈来实现。
每个终点的答案仍然是该终点前所有贡献值的前缀和。时间复杂度 \(O(n)\)。
树上做法和链上同理,无非就是多了个回溯的操作。
#include <iostream>
#include <stack>
using namespace std;
struct edge
{
int v,next;
}e[500005];
long long sum[500005],res[500005];
char s[500005];
int head[500005],fa[500005],cnt;
stack<int> sta;
void addedge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u)
{
int p=0;
if(s[u]==')')
{
if(!sta.empty())
{
p=sta.top();
sta.pop();
res[u]=res[fa[p]]+1;
}
}
else sta.push(u);
sum[u]=sum[fa[u]]+res[u];
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
if(p)sta.push(p);
else if(!sta.empty())sta.pop();
}
int main()
{
int n;
cin>>n;
cin>>(s+1);
for(int i=2;i<=n;i++)
{
cin>>fa[i];
addedge(fa[i],i);
}
dfs(1);
long long ans=0;
for(int i=1;i<=n;i++)
ans^=(sum[i]*i);
cout<<ans<<endl;
return 0;
}