树上启发式合并
合眼(安详
CF600E Lomsat gelral
详见这篇博客。
CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
题意:
一棵根为\(1\)的树,每条边上有一个字符(\(a\)-\(v\)共22种)。一条简单路径被称为\(Dokhtar-kosh\)当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中最长的\(Dokhtar-kosh\)路径的长度。
解题思路:
我有病,我去看这道题,好他妈恶心。
刚开始以为只要是它子树里出现过的字母都可以成为\(Dokhtar-kosh\)路径,于是就安详地\(fake\)了,于是颓了题解,于是试着打了一下,于是自闭了。
观察到,一条路径能成为回文路径,当且仅当其中出现奇数次的字母至多只有\(1\)个,所以我们需要关注字母出现的奇偶性,所以我们就蠢蠢欲动地想要用一些二进制制的东西去解决了。
考虑让\(a\)-\(v\)成为二进制的每一位。
设\(dis_u\)为从根节点到\(u\)的点权异或和,显然出现出现偶数次的字母位置会被异或成\(0\),奇数次的字母位置会被异或成\(1\),此时我们需要检验的就是子树中的合法状态下最长的那条路径,而合法状态显然有以下\(23\)种:
一条路径的状态可以由\(dis_u \oplus dis_v \oplus dis_{lca} \oplus dis_{lca}\)得到,由于异或的性质,我们只需要判断\(dis_u \oplus dis_v\)是否合法就可以了。所以得到:
但是直接计算的复杂度是不能忍受的\(O(23n^2)\),而且如此下去最长路径的长度也无从计算,那就开动脑筋想想其他的屑法。
-
可以开一个\(vis\)数组保存一下遍历时已有状态的最大深度,通过\(dep_u+dep_v-2 \times dep_{lca}\),解决掉计算最长路径的问题。
-
树上启发式合并,每次只删完轻儿子的信息而保留重儿子的信息,确保信息传递无误而且降低时间复杂度为\(O(23nlogn)\)。
每个节点的答案先由他的儿子来更新,更新完了之后计算该节点的子树所贡献的答案:连接子树根节点的路径和子树中的各种路径。注意不要有遗漏。
于是这道神必题就可以痛痛快快地调上一天啦(\(bushi\)
代码:
inline void dfs(int u,int typ){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==son[u]) continue;
dfs(v,0);ans[u]=max(ans[u],ans[v]);
}
if(son[u]) dfs(son[u],1);
ans[u]=max(ans[u],ans[son[u]]);
//重儿子子树中的贡献已经从里面转移过来
if(vis[dis[u]]) ans[u]=max(ans[u],vis[dis[u]]-dep[u]);
//寻找与u相连接的重儿子子树中的最优回文串
for(int i=0;i<=21;++i) if(vis[dis[u]^(1<<i)]) ans[u]=max(ans[u],vis[dis[u]^(1<<i)]-dep[u]);
vis[dis[u]]=max(dep[u],vis[dis[u]]);
//保存此状态的最大深度
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==son[u]) continue;
//在轻儿子的子树中寻找最优回文串
//注意:这里已经算上子树中节点与父亲的贡献了
//由于保存的是最大深度,所以所有子树中节点的贡献都能计算到(神!
for(int j=dfn[v];j<=low[v];++j){
int op=id[j];
if(vis[dis[op]]) ans[u]=max(ans[u],vis[dis[op]]+dep[op]-2*dep[u]);
for(int k=0;k<=21;++k) if(vis[dis[op]^(1<<k)]) ans[u]=max(ans[u],vis[dis[op]^(1<<k)]+dep[op]-2*dep[u]);
}
for(int j=dfn[v];j<=low[v];++j) vis[dis[id[j]]]=max(vis[dis[id[j]]],dep[id[j]]);
//再次计算轻儿子贡献
}
if(!typ){
for(int i=dfn[u];i<=low[u];++i) vis[dis[id[i]]]=0;
//清空轻儿子
}
}

浙公网安备 33010602011771号