正题
评测记录:https://www.luogu.org/recordnew/lists?uid=52918&pid=CF741D
题目大意
一棵根为111的树,每条边上有一个字符(a−v(a-v(a−v共222222种)))。 一条简单路径被称为Dokhtar−koshDokhtar-koshDokhtar−kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中最长的Dokhtar−koshDokhtar-koshDokhtar−kosh路径的长度。
解题思路
对于一堆重新排列后可以变成回文串的字母,仅当只有少于2个字母出现了奇数次。所以就只需要记录奇偶问题,我们可以状压一下。
求出从根到每个点的值,之后x到y的路径就可以表示为wxxorwyw_x\ xor\ w_ywx xor wy,因为LCALCALCA之上的都会相互抵消。
我们得到O(n2logn)O(n^2\ log\ n)O(n2 log n)的做法。之后用树上启发式合并就可以变为O(nlog2n)O(n\ log^2\ n)O(n log2 n)
codecodecode
#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]);
}