树上启发式合并算法概述及习题
前言
如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。
树上启发式合并概述
一、适用问题
树上启发式合并作为树上问题三剑客之一(点分治、长链剖分),以其优雅的暴力而闻名于江湖之中。
通常来说,如果一个问题可以被划分为一个个子树进行求解的问题,而且各个子儿子对答案的贡献容易添加与删除,就可以考虑使用树上启发式合并来求解。
本文主要介绍树上启发式合并的一些习题,可以从习题中仔细感受该算法的一系列特点。
二、算法介绍
树上启发式合并需要两次 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
思路:
树上的这类问题,我们可以依次思考点分治、树上启发式合并、长链剖分,根据该题题意,不难识别这是一道树上启发式合并问题。
确认是树上启发式合并之后,我们需要定状态。由于 “回文” 路径只要求所有字母可以组成一个回文串,因此我们可以利用状压的思想给每一个字母进行赋值,然后对于每个点求一个从根节点到当前点的异或和。
三种情况在代码中有比较清晰的注释,不太清楚细节的朋友可以看看代码。总体来说,这题应该属于树上启发式合并的经典问题。
#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
思路:
相比上一个问题,这个问题是更加典型的树上启发式合并问题,我们只需要维护一个全局的 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;
}
树上启发式合并算法概述及习题相关推荐
- [dsu on tree]树上启发式合并总结(算法思想及模板附例题练习)
文章目录 前言 树上启发式合并 引入 算法思想 时间复杂度 模板 练习 例题:CF600E Lomsat gelral solution code CF208E Blood Cousins solut ...
- 2019 ICPC 南昌 K. Tree(树上启发式合并,平衡树 treap)
整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 题目链接 https://nanti.jisuanke.com/t/42586 Problem 给定一 ...
- 【学习笔记】树上启发式合并
整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 树上启发式合并 模板 复杂度分析 例题 **Problem A. Arpa's letter-m ...
- (树上启发式合并)CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 Weblink https://www.luogu.com.cn/problem/CF741D Pro ...
- 2020CCPC(长春) - Strange Memory(树上启发式合并+位运算)
题目大意:给出一棵 n 个点组成的有根树,一号节点是根节点,现在要求实现 n * n 的公式: 题目分析:树上启发式合并,需要修改部分内部实现,如果可以想到树启的话,那么应该往子树上去靠拢,当每个点作 ...
- 山东理工大学第十二届ACM程序设计竞赛 - Cut the tree(树上启发式合并+线段树)
题目链接:点击查看 题目大意:给一个具有 N 个节点的有根树,以 1 号节点为根,节点编号从 1 开始,点有点权.树的第 H 层权值为深度为 H 的所有点的点权之和.树的总权值为所有层权值的最大值.问 ...
- 与图论的邂逅09:树上启发式合并
启发式树上合并 先看这样一个例题: 给定一个长度为\(10^5\)的序列,序列中都是\([0,1000000]\)的整数,接下来有\(10^5\)次询问,共两种询问:修改序列某个位置的值,以及查询区间 ...
- 【CF 600E】Lomsat gelral(树上启发式合并, dsu on tree, 静态链分治,模板题)
Algorithm 名称:树上启发式合并, dsu on tree, 静态链分治 用处:一般用来解决一类不带修改的子树查询问题 核心思想为:利用重链剖分的性质优化子树贡献的计算. 前置知识:启发式合并 ...
- 树上启发式合并问题 ---- 2019icpc南昌 K. Tree (树上启发式合并 + 动态开点线段树)
题目链接 题目大意: 就是给你一颗树,每个点有个权值viv_ivi,问你有多少对(x,y)(x,y)(x,y)满足: xxx不是yyy的祖先 yyy也不是xxx的祖先 xxx和yyy的距离不超过kk ...
- 树上启发式合并问题 ---- D. Tree and Queries[树上启发式合并+树状数组]
题目链接 题目大意: 就是给你一棵树,树上每个节点都有一个颜色,在你mmm次询问每次询问给你一个节点uuu和一个数字kkk,问你在uuu这颗子树里面又少种颜色的结点个数是大于kkk; 解题思路: 看到 ...
最新文章
- UITextView左边距为0
- 深度学习框架caffe及py-faster-rcnn详细配置安装过程
- redis持久化到mysql的方案_redis进阶: 数据持久化
- JavaScript高级之ES5 中的新增方法
- CSS 制作垂直导航
- Python界面程序实例:按钮漂移,用Python小套路来撩女神
- 剑指offer例题分享--6
- 算法笔记_面试题_6.二进制/位运算相关
- 能力风暴机器人编程 | 详解使用能力风暴机器人以及配套VJC 4.3 CH做一个物联网——智慧物流项目
- 【机器人学导论】第四章.传感器
- 已知两点坐标,求两点连成的直线中的某一点坐标
- ensembl-vep/VEP 注释软件安装及测试 超简单版(conda vep、百度网盘数据库)
- Excel多行转置为一列
- 云集品以共享经济为幌子因涉及传销被关闭,做社交电商防止误入
- 锂电池过充电、过放电、短路保护电路详解
- Win7如何开启Aero特效?
- 正则匹配-URL-域名
- 一篇文章让你从JAVA零基础入门`OOP`编程12.20
- 新买的移动硬盘,电脑USB接口插上有声音但找不到可移动硬盘
- 单选框radio总结