版权声明:原创,未经作者允许禁止转载 https://blog.csdn.net/Mr_wuyongcong/article/details/85171470
正题
评测记录:https://www.luogu.org/recordnew/lists?uid=52918&pid=CF741D
题目大意
一棵根为 的树,每条边上有一个字符 共 种 。 一条简单路径被称为 当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中最长的 路径的长度。
解题思路
对于一堆重新排列后可以变成回文串的字母,仅当只有少于2个字母出现了奇数次。所以就只需要记录奇偶问题,我们可以状压一下。
求出从根到每个点的值,之后x到y的路径就可以表示为
,因为
之上的都会相互抵消。
我们得到 的做法。之后用树上启发式合并就可以变为
#include<cstdio>
#include<algorithm>
#define N 500010
using namespace std;
struct node{
int to,next,w;
}a[N];
int tot,ls[N],size[N],dep[N],val[N];
int len[1<<22],ans[N],dfn[N],rfn[N],ed[N];
int cnt,n,son[N],dpt[N];
void addl(int x,int y,int w)//加边
{
a[++tot].to=y;
a[tot].next=ls[x];
a[tot].w=w;
ls[x]=tot;
}
void dfs(int x)//第一次搜索需要信息
{
size[x]++;
for(int i=ls[x];i;i=a[i].next)
{
int y=a[i].to;
dep[y]=dep[x]+1;
val[y]=val[x]^(1<<a[i].w);
dfs(y);
if(size[y]>size[son[x]])
son[x]=y;
size[x]+=size[y];
}
}
int get_ans(int x)//计算从x出发的最长路径长度
{
int ans=0;
if(len[val[x]])
ans=dep[x]+len[val[x]];
for(int i=0;i<22;i++)
if(len[val[x]^(1<<i)])
ans=max(ans,dep[x]+len[val[x]^(1<<i)]);
return ans;
}
void dus(int x,int top)//树上启发式合并
{
dfn[++cnt]=x;
rfn[x]=cnt;
for(int i=ls[x];i;i=a[i].next)//搜索除了最大的子树
if(a[i].to!=son[x])
{
dus(a[i].to,a[i].to);
ans[x]=max(ans[x],ans[a[i].to]);
}
int mid=cnt;
if(son[x])//搜索最大的子数
{
dus(son[x],top);
ans[x]=max(ans[x],ans[son[x]]);
}
ed[x]=cnt;
for(int i=rfn[x]+1;i<=mid;i=ed[dfn[i]]+1)//枚举子树
{
for(int j=i;j<=ed[dfn[i]];j++)//扫描这个子树的答案
ans[x]=max(ans[x],get_ans(dfn[j])-2*dep[x]);
for(int j=i;j<=ed[dfn[i]];j++)//将这个子树加入可扫描答案
len[val[dfn[j]]]=max(len[val[dfn[j]]],dep[dfn[j]]);
//分两次for不会使得起点和终点在同一棵子树
}
ans[x]=max(ans[x],get_ans(x)-2*dep[x]);//自己为起点
len[val[x]]=max(len[val[x]],dep[x]);//更新
if(x==top)//不保留该子树信息
for(int i=rfn[x];i<=ed[x];i++)
len[val[dfn[i]]]=0;
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
char c=getchar();
while (c<'a'||c>'v')
c=getchar();
addl(x,i,c-'a');
}
dep[0]=-1;
dfs(1);
dus(1,1);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}