[CF741D] Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

问题描述

Just in case somebody missed it: we have wonderful girls in Arpa’s land.

Arpa has a rooted tree (connected acyclic graph) consisting of n vertices. The vertices are numbered 1through n, the vertex 1 is the root. There is a letter written on each edge of this tree. Mehrdad is a fan of Dokhtar-kosh things. He call a string Dokhtar-kosh, if we can shuffle the characters in string such that it becomes palindrome.

He asks Arpa, for each vertex v, what is the length of the longest simple path in subtree of v that form a Dokhtar-kosh string.

输入格式

The first line contains integer n (1  ≤  n  ≤  5·105) — the number of vertices in the tree.

(n  -  1) lines follow, the i-th of them contain an integer p**i + 1 and a letter c**i + 1 (1  ≤  p**i + 1  ≤  i, c**i + 1 is lowercase English letter, between a and v, inclusively), that mean that there is an edge between nodes p**i + 1 and i + 1 and there is a letter c**i + 1 written on this edge.

输出格式

Print n integers. The i-th of them should be the length of the longest simple path in subtree of the i-th vertex that form a Dokhtar-kosh string.

样例输入输出

Examples

Input

4
1 s
2 a
3 s

Output

3 1 1 0

Input

1 a
2 h
1 a
4 h

Output

4 1 0 1 0

题意概括

有一棵树,每条边上都有一个字母,求每棵子树中最长的简单路径使这条路径上的所有字符与根节点到该点路径上的字符重新排列后能组成回文字符串。

解析

第一步,我们来分析题目中最特殊的一点:回文串。一个字符串是回文串,当且仅当字符串中最多有一个字符出现了奇数次。我们需要一个结构来表述一个字符串使我们能够清晰地知道每个字符串出现次数的奇偶。由于只有22个字母,我们可以状态压缩,每个位置上的字母出现奇数次则为1,偶数次则为0,实现可用异或运算。

既然是子树问题,我们就可以用树上启发式合并来写。设(f[i])表示状态为(i)的最长的字符串的最大深度(要求字符串没有在两棵子树中),那么每次更新答案时,假设当前节点为(x),从根节点到(x)的路径状态压缩后表示为(xor[x])。下面分两种情况讨论:

1.字符串不会跨过子树的根节点。此时又有两种情况:

  • (f[xor[x]])有值,说明在这棵子树中,有一条路径上的情况与(x)到根节点的路径一定能构成回文串(异或后全为0)。因此,有

    [ans[x]=max(ans[x],f[xor[x]]) ]

  • 假设能满足要求的路径为(path),那么(xor[x]^path)得到的结果一定是某一位为1或全为0。因此,有异或运算的性质,可以直接用(xor[x]^(1<<i))得到满足要求的状态,再更新答案,有

    [ans[x]=max(ans[x],f[xor[x]oplus(1<<i)]) ]

同时,还要更新对应的(f)值,

[f[xor[x]]=max(f[xor[sum]],dep[x]) ]

其中(dep[x])表示点(x)的深度。

2.字符串会跨过子树的根节点。那么我们可以通过情况1的方法一样来进行,只不过会对子树中每一个节点统计一次答案。具体可以用时间戳来实现模拟子树递归。另外,每遍历完一棵子树,都要记得更新一下(f)

代码

#include <iostream>
#include <cstdio>
#define N 500002
using namespace std;
int head[N],ver[N],nxt[N],edge[N],l;
int n,i,son[N],size[N],dep[N],sum[N],dfn[N],low[N],rec[N],ans[N],tim,f[1<<23];
void insert(int x,int y,char z)
{
	l++;
	ver[l]=y;
	edge[l]=(1<<(int)(z-'a'));
	nxt[l]=head[x];
	head[x]=l;
}
void dfs(int x,int pre)
{
	dfn[x]=++tim;rec[tim]=x;
	size[x]=1;dep[x]=dep[pre]+1;
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=pre){
			sum[y]=sum[x]^edge[i];
			dfs(y,x);
			size[x]+=size[y];
			if(size[y]>size[son[x]]) son[x]=y;
		}
	}
	low[x]=tim;
}
void dsu(int x,int pre)
{
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=son[x]){
			dsu(y,x);
			ans[x]=max(ans[x],ans[y]);
			for(int j=dfn[y];j<=low[y];j++) f[sum[rec[j]]]=0;
		}
	}
	if(son[x]>0){
		dsu(son[x],x);
		ans[x]=max(ans[x],ans[son[x]]);
	}
	if(f[sum[x]]) ans[x]=max(ans[x],f[sum[x]]-dep[x]);
	for(int i=0;i<22;i++){
		if(f[sum[x]^(1<<i)]) ans[x]=max(ans[x],f[sum[x]^(1<<i)]-dep[x]);
	}
	f[sum[x]]=max(f[sum[x]],dep[x]);
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=son[x]){
			for(int j=dfn[y];j<=low[y];j++){
				int z=rec[j];
				if(f[sum[z]]) ans[x]=max(ans[x],f[sum[z]]+dep[z]-2*dep[x]);
				for(int k=0;k<22;k++){
					if(f[sum[z]^(1<<k)]) ans[x]=max(ans[x],f[sum[z]^(1<<k)]+dep[z]-2*dep[x]);
				}
			}
			for(int j=dfn[y];j<=low[y];j++){
				int z=rec[j];
				f[sum[z]]=max(f[sum[z]],dep[z]);
			}
		}
	}
}
int main()
{
	cin>>n;
	for(i=1;i<n;i++){
		int u;char w;
		cin>>u>>w;
		insert(u,i+1,w);
	}
	dfs(1,0);
	dsu(1,0);
	for(i=1;i<=n;i++) cout<<ans[i]<<' ';
	cout<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/LSlzf/p/10988331.html