741D.Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(树上启发式合并+状压)
只有22种字符。
一个串满足答案的条件是:
每个字符出现次数都为偶数或者出现次数为奇数的字符仅有一个。
因此我们可以考虑:
用0表示这个字符出现次数为偶数,1表示出现次数为奇数。
而对于一个路径上的字符,我们只需要考虑每个字符的出现次数,这样我们用一个整数就可以表示出所有符合条件的状态:0或者(1<<i),0<=i<=21,总共22种。
之后我们可以考虑维护一个值\(dist_i\)表示从i到1的路径上字符的状态,其实利用异或的性质\(dist_i\)就是1到i路径里面所有字符表示的字符异或的答案。
同时我们如果知道两个点,那么两个点路径上的状态就是\(dis_u\) ^\(dis_v\)。
如果两个节点之间形成的路径符合条件的话,那么\(dis_u\)^\(dis_v=0|(1<<i),0 \leq i \leq 21\)。
用\(f_x\)表示从根节点触发路径异或之后的值为\(x\)的最大深度。
然后开始DSU On Tree,先处理出每个点的重儿子,然后轻儿子和重儿子依次合并。
时间复杂度\(O(nlogn)\)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+100;
vector<pair<int,int> > g[maxn];
int tot;
int L[maxn];//每个点进栈的时间
int R[maxn];//每个点出栈的时间
int id[maxn];//每个DFS序对应的点
int son[maxn];//重儿子
int f[maxn*20];//f(i)表示以当前节点为根的子树中状态为i的最大深度
int dis[maxn];//dis(i)表示以当前节点为根的子树中状态为i的最大深度
int ans[maxn];//记录每个点的答案
int size[maxn];//子树大小
int dep[maxn];//层数
void dfs1 (int x,int pre) {
//处理重儿子
dep[x]=dep[pre]+1;//处理出当前节点所在层数
size[x]=1;//处理出子树大小
L[x]=++tot;//DFS时入栈的时间点
id[tot]=x;//DFS序上节点本来的编号
for (pair<int,int> y:g[x]) {
dis[y.first]=dis[x]^y.second;//dis[y]记录根节点到节点外的边权异或和
dfs1(y.first,x);//往下搜
size[x]+=size[y.first];//更新子树
if (size[son[x]]<size[y.first]) son[x]=y.first;//记录重儿子
}
R[x]=tot;//记录出栈时间点
}
void cal (int x) {
//这里f(dis(x))表示在以x为根的子树状态中状态为dis(x)的最大深度
if (f[dis[x]]) ans[x]=max(ans[x],f[dis[x]]-dep[x]);//如果该节点的子树内有路径异或和为dis[x]的点,那么就先更新长度
for (int i=0;i<22;i++) {
if (f[dis[x]^(1<<i)]) {
ans[x]=max(ans[x],f[dis[x]^(1<<i)]-dep[x]);//同理,这里记录的是奇数长度回文串
}
}
f[dis[x]]=max(f[dis[x]],dep[x]);//f[dis[x]]初始化为自己
for (pair<int,int> y:g[x]) {//遍历点x的每个儿子
if (y.first==son[x]) continue;//不遍历x的重儿子
for (int j=L[y.first];j<=R[y.first];j++) {
int z=id[j];
//考虑以x为中间点的情况
//遍历整个y子树
//如果f(dis(z))可以和别的子树里的路径产生答案,那么更新答案
if (f[dis[z]]) ans[x]=max(ans[x],f[dis[z]]+dep[z]-2*dep[x]);
for (int k=0;k<22;k++) {
if (f[dis[z]^(1<<k)]) {
ans[x]=max(ans[x],f[dis[z]^(1<<k)]+dep[z]-2*dep[x]);
}
}
}
for (int j=L[y.first];j<=R[y.first];j++) {
f[dis[id[j]]]=max(f[dis[id[j]]],dep[id[j]]);
//同时把子树里的每个点的贡献统计
}
}
}
void dfs2 (int x,int kp) {
//kp为1表示处理并保留数据
//否则表示处理,否则表示处理但不保留数据
for (pair<int,int> y:g[x]) {
if (y.first==son[x]) continue;
dfs2(y.first,0);
ans[x]=max(ans[x],ans[y.first]);
//更新答案,因为有可能最大长度的链不以当前节点为根节点
}
if (son[x]) {
dfs2(son[x],1);
ans[x]=max(ans[x],ans[son[x]]);
}
cal(x);//统计节点x的答案
if (!kp) for (int i=L[x];i<=R[x];i++) f[dis[id[i]]]=0;
//如果是轻儿子,则清空数据
//如果是重儿子,就不清空,保留数据,后续把轻儿子往重儿子上合并,时间复杂度的优化主要在这里
}
int main () {
int n;
scanf("%d",&n);
for (int i=2;i<=n;i++) {
int x;
char ch;
scanf("%d %c",&x,&ch);
g[x].push_back(make_pair(i,1ll<<(ch-'a')));
}
dfs1(1,0);
dfs2(1,1);
for (int i=1;i<=n;i++) printf("%d ",ans[i]);
}