前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

树上启发式合并概述

一、适用问题

树上启发式合并作为树上问题三剑客之一(点分治、长链剖分),以其优雅的暴力而闻名于江湖之中。

通常来说,如果一个问题可以被划分为一个个子树进行求解的问题,而且各个子儿子对答案的贡献容易添加与删除,就可以考虑使用树上启发式合并来求解。

本文主要介绍树上启发式合并的一些习题,可以从习题中仔细感受该算法的一系列特点。

二、算法介绍

树上启发式合并需要两次 dfsdfsdfs,第一次 dfsdfsdfs 进行重链剖分,第二次 dfsdfsdfs 进行求解。

通常有一个全局的数组用于信息记录。dfsdfsdfs 之前,需要将这个数组赋初值。dfsdfsdfs 时,先递归处理轻儿子,处理完之后清空轻儿子,最后再处理重儿子,处理完之后不清空。

计算完儿子的答案之后,再递归所有轻儿子,边递归边计算答案,并将轻儿子的信息添加到全局数组中。

这个做法的时间复杂度是 O(nlogn)O(nlogn)O(nlogn),因为每个节点直接继承了其子树中的重儿子,即每次只有轻儿子会被重复访问,访问完之后,轻儿子即会和重儿子进行合并,每次合并 szszsz 至少乘 222,因此每个点最多被重复访问 lognlognlogn 次,即总时间复杂度为 O(nlogn)O(nlogn)O(nlogn)。

三、算法模板

其实树上启发式合并并没有什么模板,只需要处理好两次 dfsdfsdfs 的过程,然后实现插入、删除、更新三个函数即可。

以下面第一题的代码为例,给出一个大致的模板。

int sz[N],son[N];void dfs1(int x){/* 求解重儿子 */sz[x] = 1;for(int i = head[x]; i; i = e[i].next){int y = e[i].to;dfs1(y); sz[x] += sz[y];if(sz[y] > sz[son[x]]) son[x] = y;}
}void Delete(int x){/* 删除的内容 */for(int i = head[x]; i; i = e[i].next) Delete(e[i].to);
}void modify(int x,int fa){/* 更新的内容 */for(int i = head[x]; i; i = e[i].next) modify(e[i].to,fa);
}void ins(int x){/* 插入的内容 */for(int i = head[x]; i; i = e[i].next) ins(e[i].to);
}void dfs2(int x){/* 求解轻儿子并清空 */for(int i = head[x]; i; i = e[i].next)if(e[i].to != son[x]) dfs2(e[i].to), Delete(e[i].to);/* 求解重儿子并保留 */if(son[x]) dfs2(son[x]);/* 用重儿子更新答案 *//* 枚举轻儿子更新答案,并加入轻儿子 */for(int i = head[x]; i; i = e[i].next) if(e[i].to != son[x]) modify(e[i].to,x), ins(e[i].to);/* 用所有儿子更新答案 */
}

树上启发式合并系列习题

1. Mehrdad’s Dokhtar-kosh paths

题意: 给定一棵有根树,每条边的权值是 [a,v][a,v][a,v] 的一个字母。现对于每个树上点,求出最长的一条 “回文” 路径。“回文” 路径的含义是将路径上所有的字母取出,可以组成一个回文串。(1≤n≤5∗105)(1\leq n\leq 5*10^5)(1≤n≤5∗105)

思路: 树上的这类问题,我们可以依次思考点分治、树上启发式合并、长链剖分,根据该题题意,不难识别这是一道树上启发式合并问题。

确认是树上启发式合并之后,我们需要定状态。由于 “回文” 路径只要求所有字母可以组成一个回文串,因此我们可以利用状压的思想给每一个字母进行赋值,然后对于每个点求一个从根节点到当前点的异或和。

令 f[i]f[i]f[i] 表示对于当前点 nownownow,其子树中存在一个点 xxx,value[x]=i,f[i]=dep[x]value[x]=i,f[i]=dep[x]value[x]=i,f[i]=dep[x],xxx 为此中情况下深度最深的点。然后计算点 nownownow 答案时,我们只需考虑三种情况。

  1. nownownow 为最长路径的一个端点
  2. 最长路径经过 nownownow
  3. 最长路径在 nownownow 子树中,不经过 nownownow

三种情况在代码中有比较清晰的注释,不太清楚细节的朋友可以看看代码。总体来说,这题应该属于树上启发式合并的经典问题。

总结: 此题有几个思想比较可取。

  1. 对每一个字母进行状压编码
  2. 每一个点维护的权值是从根到该点的异或和
  3. 分 333 种情况对答案进行了枚举

代码:

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int inf = 1e8;
const int N = 5e5+10;
const db EPS = 1e-9;
using namespace std;void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}int n,a[N],tot,head[N],sz[N],son[N],f[1<<22],dep[N],ans[N]; //f[i]:表示子树中异或和为f[i]的最大深度
struct Node{int to,next;
}e[N];void add(int x,int y){e[++tot].to = y, e[tot].next = head[x], head[x] = tot;
}void dfs1(int x){ans[x] = -inf; sz[x] = 1;for(int i = head[x]; i; i = e[i].next){int y = e[i].to;dep[y] = dep[x]+1; a[y] ^= a[x];dfs1(y); sz[x] += sz[y];if(sz[y] > sz[son[x]]) son[x] = y;}
}void Delete(int x){f[a[x]] = -inf;for(int i = head[x]; i; i = e[i].next) Delete(e[i].to);
}void modify(int x,int fa){ans[fa] = max(ans[fa],f[a[x]]+dep[x]-2*dep[fa]);for(int i = 0; i < 22; i++)ans[fa] = max(ans[fa],f[a[x]^(1<<i)]+dep[x]-2*dep[fa]);for(int i = head[x]; i; i = e[i].next) modify(e[i].to,fa);
}void ins(int x){f[a[x]] = max(f[a[x]],dep[x]);for(int i = head[x]; i; i = e[i].next) ins(e[i].to);
}void dfs2(int x){ans[x] = 0;for(int i = head[x]; i; i = e[i].next)if(e[i].to != son[x]) dfs2(e[i].to), Delete(e[i].to);if(son[x]) dfs2(son[x]);f[a[x]] = max(f[a[x]],dep[x]);//路径经过xfor(int i = head[x]; i; i = e[i].next) if(e[i].to != son[x]) modify(e[i].to,x), ins(e[i].to);//x为路径端点ans[x] = max(ans[x],f[a[x]]-dep[x]);for(int i = 0; i < 22; i++)ans[x] = max(ans[x],f[a[x]^(1<<i)]-dep[x]);//路径不经过xfor(int i = head[x]; i; i = e[i].next) ans[x] = max(ans[x],ans[e[i].to]);
}int main()
{scanf("%d",&n); tot = 1;rep(i,0,(1<<22)-1) f[i] = -inf; //不要忘记赋初值rep(i,2,n){int p; char s[10];scanf("%d%s",&p,s);add(p,i); a[i] = 1<<(s[0]-'a');}dfs1(1); dfs2(1);rep(i,1,n) printf("%d%c",ans[i]," \n"[i==n]);return 0;
}
2. Treediff

题意: 给定一棵有根树,一共有 nnn 个节点,mmm 个叶子,每个叶子节点都有一个权值。现对于每个非叶子节点,要求求出其子树中任意两个叶子权值绝对值之差的最小值。 (1≤n≤5∗104)(1\leq n\leq 5*10^4)(1≤n≤5∗104)

思路: 相比上一个问题,这个问题是更加典型的树上启发式合并问题,我们只需要维护一个全局的 setsetset,然后每次往 setsetset 加点时,用其左右两个点求一个差值,然后更新答案即可。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int inf = (1ll<<31)-1ll;
const int N = 5e4+10;
const db EPS = 1e-9;
using namespace std;void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}int n,m,a[N],tot,head[N],sz[N],son[N],ans[N]; //f[i]:表示子树中异或和为f[i]的最大深度
struct Node{int to,next;
}e[N];
set<int> st;void add(int x,int y){e[++tot].to = y, e[tot].next = head[x], head[x] = tot;
}void dfs1(int x){sz[x] = 1;for(int i = head[x]; i; i = e[i].next){int y = e[i].to;dfs1(y); sz[x] += sz[y];if(sz[y] > sz[son[x]]) son[x] = y;}
}void ins(int x,int fa){if(a[x] != inf){if(st.find(a[x]) != st.end()) ans[fa] = 0;else{st.insert(a[x]);// logs(x,ans[x],fa,ans[fa]);auto it = st.find(a[x]), tmp = it;if(it != st.begin()){tmp = --it; it++;ans[fa] = min(ans[fa],(*it)-(*tmp));}tmp = ++it; it--;if(tmp != st.end()) ans[fa] = min(ans[fa],(*tmp)-(*it));// for(auto &v:st) logs(v);}}for(int i = head[x]; i; i = e[i].next) ins(e[i].to,fa);
}void dfs2(int x){for(int i = head[x]; i; i = e[i].next)if(e[i].to != son[x]) dfs2(e[i].to), st.clear();if(son[x]) dfs2(son[x]), ans[x] = min(ans[x],ans[son[x]]);if(a[x] != inf) st.insert(a[x]);for(int i = head[x]; i; i = e[i].next) if(e[i].to != son[x]) ins(e[i].to,x);
}int main()
{scanf("%d%d",&n,&m); tot = 1;rep(i,2,n){int p; scanf("%d",&p);add(p,i);}rep(i,1,n) ans[i] = inf, a[i] = inf;rep(i,n-m+1,n) scanf("%d",&a[i]);dfs1(1); dfs2(1);rep(i,1,n-m) printf("%d%c",ans[i]," \n"[i==n]);return 0;
}

树上启发式合并算法概述及习题相关推荐

  1. [dsu on tree]树上启发式合并总结(算法思想及模板附例题练习)

    文章目录 前言 树上启发式合并 引入 算法思想 时间复杂度 模板 练习 例题:CF600E Lomsat gelral solution code CF208E Blood Cousins solut ...

  2. 2019 ICPC 南昌 K. Tree(树上启发式合并,平衡树 treap)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 题目链接 https://nanti.jisuanke.com/t/42586 Problem 给定一 ...

  3. 【学习笔记】树上启发式合并

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 树上启发式合并 模板 复杂度分析 例题 **Problem A. Arpa's letter-m ...

  4. (树上启发式合并)CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 Weblink https://www.luogu.com.cn/problem/CF741D Pro ...

  5. 2020CCPC(长春) - Strange Memory(树上启发式合并+位运算)

    题目大意:给出一棵 n 个点组成的有根树,一号节点是根节点,现在要求实现 n * n 的公式: 题目分析:树上启发式合并,需要修改部分内部实现,如果可以想到树启的话,那么应该往子树上去靠拢,当每个点作 ...

  6. 山东理工大学第十二届ACM程序设计竞赛 - Cut the tree(树上启发式合并+线段树)

    题目链接:点击查看 题目大意:给一个具有 N 个节点的有根树,以 1 号节点为根,节点编号从 1 开始,点有点权.树的第 H 层权值为深度为 H 的所有点的点权之和.树的总权值为所有层权值的最大值.问 ...

  7. 与图论的邂逅09:树上启发式合并

    启发式树上合并 先看这样一个例题: 给定一个长度为\(10^5\)的序列,序列中都是\([0,1000000]\)的整数,接下来有\(10^5\)次询问,共两种询问:修改序列某个位置的值,以及查询区间 ...

  8. 【CF 600E】Lomsat gelral(树上启发式合并, dsu on tree, 静态链分治,模板题)

    Algorithm 名称:树上启发式合并, dsu on tree, 静态链分治 用处:一般用来解决一类不带修改的子树查询问题 核心思想为:利用重链剖分的性质优化子树贡献的计算. 前置知识:启发式合并 ...

  9. 树上启发式合并问题 ---- 2019icpc南昌 K. Tree (树上启发式合并 + 动态开点线段树)

    题目链接 题目大意: 就是给你一颗树,每个点有个权值viv_ivi​,问你有多少对(x,y)(x,y)(x,y)满足: xxx不是yyy的祖先 yyy也不是xxx的祖先 xxx和yyy的距离不超过kk ...

  10. 树上启发式合并问题 ---- D. Tree and Queries[树上启发式合并+树状数组]

    题目链接 题目大意: 就是给你一棵树,树上每个节点都有一个颜色,在你mmm次询问每次询问给你一个节点uuu和一个数字kkk,问你在uuu这颗子树里面又少种颜色的结点个数是大于kkk; 解题思路: 看到 ...

最新文章

  1. UITextView左边距为0
  2. 深度学习框架caffe及py-faster-rcnn详细配置安装过程
  3. redis持久化到mysql的方案_redis进阶: 数据持久化
  4. JavaScript高级之ES5 中的新增方法
  5. CSS 制作垂直导航
  6. Python界面程序实例:按钮漂移,用Python小套路来撩女神
  7. 剑指offer例题分享--6
  8. 算法笔记_面试题_6.二进制/位运算相关
  9. 能力风暴机器人编程 | 详解使用能力风暴机器人以及配套VJC 4.3 CH做一个物联网——智慧物流项目
  10. 【机器人学导论】第四章.传感器
  11. 已知两点坐标,求两点连成的直线中的某一点坐标
  12. ensembl-vep/VEP 注释软件安装及测试 超简单版(conda vep、百度网盘数据库)
  13. Excel多行转置为一列
  14. 云集品以共享经济为幌子因涉及传销被关闭,做社交电商防止误入
  15. 锂电池过充电、过放电、短路保护电路详解
  16. Win7如何开启Aero特效?
  17. 正则匹配-URL-域名
  18. 一篇文章让你从JAVA零基础入门`OOP`编程12.20
  19. 新买的移动硬盘,电脑USB接口插上有声音但找不到可移动硬盘
  20. 单选框radio总结

热门文章

  1. 家用nas的过去现在和未来--2008n年
  2. 在Xen的DomU中安装MySQL
  3. WIFI篇(3.python破解wifi--pywifi的介绍)
  4. python try except continue_python中 try、except、finally执行顺序
  5. Python(六):dict、set
  6. 添加mysql.h头文件
  7. 【SGU495】Kids and Prizes(概率dp)
  8. 细数继承与派生(纯干货推荐)1.0
  9. Python入门经典学习1-乳腺癌分类问题
  10. mmlspark-102 : 简单的ML Pipelines