树链剖分 — 轻重边路径剖分
来源:互联网 发布:韩国制衣软件 编辑:程序博客网 时间:2024/04/30 19:36
树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
首先我们有一颗树每个点(或者边)有权值,我们要做的就是询问两个点之间路径上各点(边)权值的最大、最小,权值和(就是线段树能干的),然后我们还要支持在线更改任意节点(边)的权值。
我们要做的是轻重链剖分,首先我们看几个定义
size:和SBT里的一样,size[i]为以该点为根节点的子树一共有几个节点。
重儿子:一个节点当不为叶子节点的时候有且只有一个重儿子,重儿子为该点的儿子中size最大的,有多个最大时任选一个。
重链:由根节点开始,每个点每次都访问自己的重儿子,一直访问到叶子节点,就组成了一条重链
那么对于一个点的非重儿子来说,以他为根节点,可以重新访问出一条重链
如图所示,用红色的线画出的为重链,其中6号点自己为一条重链,那么对于每条重链,我们需要记下他的顶标top,就是该重链中深度最小的节点的标号
比如链1-3-4-9-10,的top为1,链2-8的top为2。
重链几个明显的性质就是互不重合且所有重链覆盖所有点,重链之间由一条不在重链上的边(我们称作轻边)连接,然后对于每一条重链来说,我们定义他的深度,顶标为根节点的重链的深度为1,顶标的父亲在深度为x的重链上,那么该重链深度为x+1,如图链1-3-4-9-10的深度为1,链2-8,链5-7的深度为2,链6的深度为3。
那么我们首先DFS,可以求出每个点的size,然后再深搜一遍可得到每个点的top,和处理出每一条链。
有了所有的重链,覆盖每一个点,然后我们要处理的问题是两点之间的最值等问题。
有些像线段树,假设我们需要求一条重链上的最大值,那么我们需要将重链存进线段树,且重链上的所有点的编号是连续的(区间),那么我们要对所有的点以深搜序重新编号(dfs进行树链剖分的时候顺便标记一下就好了),那么我们可以用线段树来存树上点的值(边的权值也一样,可以将边下放到点 **重要思想**),这样对于在同一条重链上的点我们就可以在logn的时间里求出值了,然后对于不同链上的点,我们先给他们升到同一深度上的链,同时更新答案,然后做就好了(上升到同一深度上的重链这个过程有点类似于LCA倍增,noip2013 Day1 T3 火车运输,其实这道题用树链剖分的水题)至于每个点的权值修改就是线段树的事了。
算法实现:
⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记w[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
如图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。
**数据规模大时,递归可能会爆栈,而非递归dfs会很麻烦,所以可将两个dfs改为宽搜+循环。即先宽搜求出fa、dep,然后逆序循环求出siz、son,再顺序循环求出top和w。
练手题目:spoj375。
#include <cstdio>#include <algorithm>#include <iostream>#include <string.h>using namespace std;const int maxn = 100010;struct Tedge{int b, next;} e[maxn*2]; // 存树上的边int tree[maxn]; // 线段树, 存重链int CNT, n, tot, edge, root, a, b, c; // tot 记录线段树(区间上)点的数量int d[maxn][3]; // d[][ from|to|weight ] d[][]数组存储n-1条边的信息// first[] 每个点的邻边链式前向星存储, dep[u]记录在以root为根时,顶点在树上的深度// w[u]表示u在重链线段树上的序号, top[u]记录u所在重链的最小顶点标号//son[]记录每个顶点的重儿子编号, siz[u]顶点u的孩子节点数, fa[]记录每个顶点的父亲编号int first[maxn], dep[maxn], w[maxn], fa[maxn], top[maxn], son[maxn], siz[maxn];char ch[10];void insert(int a, int b, int c) // 插入边,建树{ e[++edge].b = b; e[edge].next = first[a]; first[a] = edge;}void dfs(int v){ siz[v] = 1,son[v] = 0; for (int i = first[v]; i > 0; i = e[i].next) if (e[i].b != fa[v]) { fa[e[i].b] = v; dep[e[i].b] = dep[v]+1; dfs(e[i].b); if (siz[e[i].b] > siz[son[v]]) son[v] = e[i].b; siz[v] += siz[e[i].b]; }}void build_tree(int v, int tp){ w[v] = ++tot, top[v] = tp; if (son[v] != 0) // 非叶子节点必然含有重儿子,为保证重链在线段树(区间)上标号连续 build_tree(son[v], top[v]); // 先递归建立重儿子 for ( int i = first[v]; i > 0; i = e[i].next) if (e[i].b != son[v] && e[i].b != fa[v]) // 点的非重儿子,以他为根节点,可以重新访问出一条重链 build_tree(e[i].b, e[i].b);}void update(int root, int left, int right, int pos, int x){ if (pos > right || left > pos) return; if (left == right) { tree[root] = x;return;}int mid = (left + right) / 2, ls=root*2, rs=ls+1;update(ls, left, mid, pos, x);update(rs, mid+1, right, pos, x);tree[root] = max(tree[ls], tree[rs]);}int maxi(int root, int left, int right, int l, int r){ if (l > right || r < left) return 0; if (l <= left && right <= r) return tree[root]; int mid = (left + right) / 2, ls = root * 2, rs = ls + 1; return max(maxi(ls, left, mid, l, r), maxi(rs, mid+1, right, l, r));}inline int find(int va, int vb){ int f1 = top[va], f2 = top[vb], tmp = 0; while (f1 != f2) { if (dep[f1] < dep[f2]) { swap(f1, f2); swap(va, vb); } tmp = max(tmp, maxi(1, 1, tot, w[f1], w[va])); va = fa[f1]; f1 = top[va]; } if (va == vb) return tmp; if (dep[va] > dep[vb]) swap(va, vb); return max(tmp, maxi(1, 1, tot, w[son[va]], w[vb])); // 由于树上边的权值是下放到点上,线段树son[va]->vb}void init(){ scanf("%d", &n); root = (n + 1) / 2; // 将一棵无根树转为有根树,其中取中间编号的节点为根 root fa[root] = tot = dep[root] = edge = 0; memset(siz, 0, sizeof(siz)); memset(first, 0, sizeof(first)); memset(tree, 0, sizeof(tree)); for (int i = 1; i < n; i++) { scanf("%d%d%d", &a, &b, &c); d[i][0] = a; d[i][1] = b; d[i][2] = c; insert(a, b, c); insert(b, a, c); } dfs(root); build_tree(root, root); // 建立重链的线段树 for (int i = 1; i < n; i++) { if (dep[d[i][0]] > dep[d[i][1]]) // 调整树上 每条边两端顶点序号 保证边 a->b 有a<b swap(d[i][0], d[i][1]); update(1, 1, tot, w[d[i][1]], d[i][2]); // 初始化树中的每一条边在重链线段树上的区间初值 }}void work(){ scanf("%s",ch); for (; ch[0] != 'D'; scanf("%s",ch)) { scanf("%d%d", &a, &b); if (ch[0] == 'Q') printf("%d\n", find(a, b)); else update(1, 1, tot, w[d[a][1]], b); }}int main(){ freopen("data.txt","r",stdin); freopen("data.out","w",stdout); init(); work(); return 0;}线段树,好久不写,难调!!!
- 树链剖分 — 轻重边路径剖分
- 选区转路径&画笔描边路径——邮票
- 关于轻重边及树链剖分该怎么写...
- 轻重链剖分
- svg描边路径动画
- 12球,轻重问题
- 红尘轻重间
- [JZOJ4468][JSOI2016?]轻重路径
- [JSOI2016]轻重路径
- 测豆子的轻重问题
- 【GDOI2016模拟4.23】轻重路径
- 庄家仓位轻重可进行大体估算
- 三言二拍:轻重与快慢(转帖:来自keso)
- 无权无向图之最少边路径
- 有负边路径的话用SPFA算法
- 每个人都会有一些偏见只不过有轻重而已
- 12个乒乓球称3次,分出轻重
- Codeforces 398D Instant Messanger 轻重点分类 模拟
- 用Oracle的函数,判断点是否在多边形内
- for update 数据库锁机制(mysql下测试)
- bzoj 4402 Claris的剑 组合数学
- GitHub查找优秀的开源项目和一些资源福利
- JAVA基础
- 树链剖分 — 轻重边路径剖分
- java线程同步的三种方法[synchronized关键字,Lock加锁,信号量Semaphore]
- JS跨域请求
- 拯救你的数据 通过日志恢复MSSQL数据
- CentOS7安装配置mysql5.7
- 更新UI操作需要在主线程完成
- 基于Java Socket的自定义协议
- 进程与线程的区别与联系
- 八种排序算法