树上启发式合并

合眼(安详

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\)种:

\[0000000000000000000000 \\ 1000000000000000000000 \\ 0100000000000000000000 \\ 0010000000000000000000 \\ ... \]

一条路径的状态可以由\(dis_u \oplus dis_v \oplus dis_{lca} \oplus dis_{lca}\)得到,由于异或的性质,我们只需要判断\(dis_u \oplus dis_v\)是否合法就可以了。所以得到:

\[dis_u \oplus dis_v=2^i \ \ -> dis_u \oplus 2^i= dis_v\\ dis_u \oplus dis_v=0 \ \ \ -> dis_u = dis_v \]

但是直接计算的复杂度是不能忍受的\(O(23n^2)\),而且如此下去最长路径的长度也无从计算,那就开动脑筋想想其他的屑法。

  1. 可以开一个\(vis\)数组保存一下遍历时已有状态的最大深度,通过\(dep_u+dep_v-2 \times dep_{lca}\),解决掉计算最长路径的问题。

  2. 树上启发式合并,每次只删完轻儿子的信息而保留重儿子的信息,确保信息传递无误而且降低时间复杂度为\(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;
        //清空轻儿子
    }
}

posted @ 2022-08-17 20:11  Broken_Eclipse  阅读(64)  评论(0)    收藏  举报

Loading