【学习笔记】Kruskal 重构树(BZOJ3551【ONTAK2010】Peaks加强版)
1. 例题引入:BZOJ3551
- 用一道例题引入:BZOJ3551
题目大意:有 NNN 座山峰,每座山峰有他的高度 hih_ihi。有些山峰之间有双向道路相连,共 MMM 条路径,每条路径有一个困难值,这个值越大表示越难走,现在有 QQQ 组询问,每组询问询问从点 vvv 开始只经过困难值小于等于 xxx 的路径所能到达的山峰中第 kkk 高的山峰的高度,如果无解输出 −1-1−1。强制在线。
- 这道题的离线做法可以是线段树合并,可以参照我之前写过的一篇文章,里面有提到:【学习笔记】线段树的扩展(线段树的合并与分裂、可持久化线段树)
- 强制在线的话,我们似乎没有什么好思路。
- 先不考虑求第 kkk 大的权值,我们先考虑快速判断点对 (u,v)(u,v)(u,v) 能否通过边权不超过 xxx 的边互相到达。
- 从最优化的角度考虑,把问题转化为从点 uuu 出发,寻找一条 (u,v)(u,v)(u,v) 之间的路径,使得这条路径的边权最大值最小。我们需要判断的是这个最小的最大边权是否不超过 xxx。
- 因为无向图形态固定,所以我们要找到一条 (u,v)(u,v)(u,v) 之间的路径,使得这条路径的边权最大值最小,实际上就是找到最小生成树中的 (u,v)(u,v)(u,v) 之间的路径的最大边权。
- 然而如果只是查询最大边权写个树上倍增就没了。
- 这里我们需要访问从点 uuu 出发,能到达所有点的集合。很明显,这个集合是一个连通块,在 KruskalKruskalKruskal 算法的过程中,这个连通块必然在某一时刻是完整存在于一个集合的(因为边一定从小到大接进来的),我们利用这一点,可以将点按照它们之间能到达的最大边权进行分类,于是就有了 KruskalKruskalKruskal 重构树。
2. Kruskal 重构树的构造过程
- 具体做法:
- 我们把新构建出的图叫做重构树,开始重构树中只有 nnn 个孤立的点,我们将它们的点权视作 −∞-\infty−∞。
- 在 KruskalKruskalKruskal 算法求最小生成树的过程中,遇到一条连接两个不同集合的边,我们在并查集中分别找到两个集合的根 u,vu,vu,v,新建一个结点 www,合并两个集合,并且令 www 为新集合的根。
- 在重构树中将 www 作为 u,vu,vu,v 共同的父亲,即在重构树中连边 w→u,w→vw\to u,w\to vw→u,w→v。令 www 的点权为 (u,v)(u,v)(u,v) 的边权。
3. Kruskal 重构树的性质
- 根据此构造过程,我们可以得到关于重构树的性质:
- 重构树是一棵二叉树,且满足大根堆的性质。
- 原图中的 nnn 个点均为重构树中的叶子结点。
- 对于点对 (u,v)(u,v)(u,v),它们在原图中的所有路径中,最大边权最小的路径的最大边权为,u,vu,vu,v 在重构树中 lcalcalca 的权值。
- 对于一个叶子结点 uuu,它在原图中经过边权不超过 xxx 的边,能到达的点集为:找到一个深度最小的 uuu 的祖先 vvv,使得 vvv 的点权不超过 xxx,根据 KruskalKruskalKruskal 算法的过程和重构树的性质,可以知道,vvv 的子树中的叶子结点集合即为能到达的点集。对于一个叶子结点 uuu,它在原图中经过边权不超过 xxx 的边,能到达的点集为:找到一个深度最小的 uuu 的祖先 vvv,使得 vvv 的点权不超过 xxx,根据 KruskalKruskalKruskal 算法的过程和重构树的性质,可以知道,vvv 的子树中的叶子结点集合即为能到达的点集。
4. 回到例题:BZOJ3551
- 那么题目中的询问,我们利用性质4,在重构树中找到深度最小的满足条件的结点 uuu。
- 然后求子树的叶子节点中的 kkk 大权值,我们可以转化为 dfsdfsdfs 序的区间内的 kkk 大权值,然后就是经典的静态区间 kkk 大问题。
- 这个只需要对 dfsdfsdfs 序的每个前缀 1…i1\dots i1…i 利用主席树(可持久化线段树)维护出权值线段树的每个值域区间的元素个数,查询时候只需要差分一下,在两棵权值线段树上二分即可。
- 如果不清楚静态区间 kkk 大的可以自行百度搜索一下主席树。
- 总结一下,对于图的形态不变,并且需要限制通过边权小于或大于某个值的边,关于点的连通性的在线查询问题,可以考虑 KruskalKruskalKruskal 重构树。
5. 相关题目
- UOJ #407 【IOI2018】狼人
- UOJ #393 【NOI2018】归程
附:例题 BZOJ3551 代码
#include <bits/stdc++.h>inline char nextChar()
{static const int buffer_size = 2333333; static char buffer[buffer_size]; static const char *tail = buffer + buffer_size; static char *head = buffer + buffer_size; if (head == tail){fread(buffer, 1, buffer_size, stdin); head = buffer; }return *head++;
}template <class T>
inline void read(T &x)
{static char ch; while (!isdigit(ch = nextChar())); x = ch - '0'; while (isdigit(ch = nextChar()))x = x * 10 + ch - '0';
}inline void putChar(char ch)
{static const int buffer_size = 2333333; static char buffer[buffer_size]; static const char *tail = buffer + buffer_size; static char *head = buffer; if (ch == '\0')fwrite(buffer, 1, head - buffer, stdout); *head++ = ch; if (head == tail)fwrite(buffer, 1, buffer_size, stdout), head = buffer;
}template <class T>
inline void putint(T x)
{static char buf[22]; static char *tail = buf; if (!x) return (void)(putChar('0')); if (x < 0) x = ~x + 1, putChar('-'); for (; x; x /= 10) *++tail = x % 10 + '0'; for (; tail != buf; --tail) putChar(*tail);
}const int MaxNV = 2e5 + 5;
const int MaxNE = 5e5 + 5;
const int MaxLog = 20; const int MaxS = MaxNV * 30; struct edge
{int u, v, w; inline bool operator < (const edge &rhs) const{return w < rhs.w; } inline void scan(){read(u), read(v), read(w); }
}e[MaxNE]; struct halfEdge
{int v; halfEdge *next;
}adj_pool[MaxNE], *adj[MaxNV], *adj_tail = adj_pool; int n, m, Q, dfs_clock, last_ans, tot; int rt, cnt, idx[MaxNV], seg[MaxNV];
int h[MaxNV], lef[MaxNV], rit[MaxNV];
int dep[MaxNV], anc[MaxNV][MaxLog + 1]; int ufs_fa[MaxNV], val[MaxNV];
int real_num, real[MaxNV]; int lc[MaxS], rc[MaxS], sze[MaxS]; inline void addEdge(int u, int v)
{adj_tail->v = v; adj_tail->next = adj[u]; adj[u] = adj_tail++;
}inline int ufs_find(int x)
{return x == ufs_fa[x] ? x : ufs_fa[x] = ufs_find(ufs_fa[x]);
}inline int jump(int u, int k)
{for (int i = MaxLog; i >= 0; --i)if (anc[u][i] && val[anc[u][i]] <= k)u = anc[u][i]; return u;
}inline void dfs_init(int u)
{if (u <= n) idx[lef[u] = ++dfs_clock] = u; for (int i = 0; anc[u][i]; ++i)anc[u][i + 1] = anc[anc[u][i]][i]; for (halfEdge *e = adj[u]; e; e = e->next){anc[e->v][0] = u; dfs_init(e->v); if (!lef[u]) lef[u] = lef[e->v]; }rit[u] = dfs_clock;
}inline void insert(int lst, int &x, int l, int r, int pos)
{x = ++tot; lc[x] = lc[lst], rc[x] = rc[lst], sze[x] = sze[lst] + 1; if (l == r) return; int mid = l + r >> 1; pos <= mid ? insert(lc[lst], lc[x], l, mid, pos) : insert(rc[lst], rc[x], mid + 1, r, pos);
}inline int query(int x, int y, int l, int r, int k)
{if (l == r) return l; int mid = l + r >> 1, rsze = sze[rc[x]] - sze[rc[y]]; return k <= rsze ? query(rc[x], rc[y], mid + 1, r, k) : query(lc[x], lc[y], l, mid, k - rsze);
}int main()
{read(n), read(m), read(Q); for (int i = 1; i <= n; ++i){read(h[i]); real[++real_num] = h[i]; }std::sort(real + 1, real + real_num + 1); real_num = std::unique(real + 1, real + real_num + 1) - real - 1; for (int i = 1; i <= n; ++i){ufs_fa[i] = i; h[i] = std::lower_bound(real + 1, real + real_num + 1, h[i]) - real; }cnt = n; for (int i = 1; i <= m; ++i)e[i].scan(); std::sort(e + 1, e + m + 1); for (int i = 1; i <= m; ++i){int u = ufs_find(e[i].u), v = ufs_find(e[i].v); if (u != v){val[++cnt] = e[i].w; ufs_fa[u] = ufs_fa[v] = ufs_fa[cnt] = cnt; addEdge(cnt, u), addEdge(cnt, v); }}rt = ufs_find(1); dfs_init(rt); for (int i = 1; i <= n; ++i)insert(seg[i - 1], seg[i], 1, real_num, h[idx[i]]); while (Q--){int u, x, k; read(u), read(x), read(k); u = jump(u, x); last_ans = k <= rit[u] - lef[u] + 1 ? real[query(seg[rit[u]], seg[lef[u] - 1], 1, real_num, k)] : 0; putint(last_ans ? last_ans : -1); putChar('\n'); }putChar('\0'); return 0;
}
【学习笔记】Kruskal 重构树(BZOJ3551【ONTAK2010】Peaks加强版)相关推荐
- bzoj3551 [ONTAK2010]Peaks加强版 kruskal重构树
最大边最小,就是最小生成树,可以考虑用主席树维护father数组,但不能遍历子集查找 然后就只能牺牲一部分空间时间来多存一些东西 多存的就是边值大小顺序,本来以为是用主席树排边值,结果由于此题有2种数 ...
- bzoj3551: [ONTAK2010]Peaks加强版
很明显只有最小生成树里面的点有用 我会一个离线的做法,把询问边长排序,逐步合并树,启发式合并splay 在线怎么做呢? 考虑合并出最小生成树的过程,两点合并是并不是一边连向一边而是建出新点,并将新点连 ...
- ONTAK2010 Peaks加强版(离线在线)
题面 弱化版:luogu 强制在线版:bzoj 题解 本题有两种解法 离线算法:线段树合并 先看一道简单题[USACO18JAN]MooTube 本题就是在此基础上求第\(k\)高的点 首先把询问和路 ...
- #3551. [ONTAK2010]Peaks加强版(kruskal 重构树 + 主席树)
#3551. [ONTAK2010]Peaks加强版 我们要求从一个点出发经过困难值小于等于xxx的路径所能到达的山峰中第kkk高的是什么. 考虑按照边权升序,建议kruskalkruskalkrus ...
- Kruskal重构树 学习笔记
Kruskal重构树 学习笔记 文章目录 Kruskal重构树 学习笔记 前言 例题1 BZOJ3732 Network 例题2 [NOI2018] 归程 前言 Kruskal重构树是一种比较冷门的算 ...
- [ONTAK2010] Peaks加强版 (kruskal重构树+主席树+倍增)
Peaks description solution code description 在Bytemountains有N座山峰,每座山峰有他的高度h_i 有些山峰之间有双向道路相连,共M条路径,每条路 ...
- kruskal 重构树(讲解 + 例题)
kruskal重构树 如何建树 模仿kruskalkruskalkruskal,先将所有边排序. 依次遍历每一条边,如果这条边的两个节点(u,vu, vu,v)不在同一个连通块里面, 则新建一个nod ...
- kruskal重构树练习
洛谷 P4197 Peaks 题意: 有 nnn 个山峰,每一个山峰高 hih_ihi ,有 mmm 条双向带权边将一些山峰连接起来,有 qqq 次询问,每次询问 (v,x,k)(v,x,k)(v, ...
- 【NOI 2018】归程(Kruskal重构树)
题面在这里就不放了. 同步赛在做这个题的时候,心里有点纠结,很容易想到离线的做法,将边和询问一起按水位线排序,模拟水位下降,维护当前的各个联通块中距离$1$最近的距离,每次遇到询问时输出所在联通块的信 ...
- LOJ.2865.[IOI2018]狼人(Kruskal重构树 主席树)
LOJ 洛谷 这题不就是Peaks(加强版)或者归程么..这算是\(IOI2018\)撞上\(NOI2018\)的题了? \(Kruskal\)重构树(具体是所有点按从小到大/从大到小的顺序,依次加入 ...
最新文章
- 未能加载文件或程序集“Report.Basic”或它的某一个依赖项。试图加载格式不正确的程序...
- Software caused connection abort: recv failed
- Thinkphp3.2学习(一)
- 热修复框架Tinker的从0到集成之路(转)
- IT规划是文档还是行动?
- phpstudy php日志,phpstudy开启网站Apache日志并且按照日期划分创建
- torchvision.transforms包的使用
- ProtoBuf的介绍以及在Java中使用protobuf将对象进行序列化与反序列化
- 基于docker 如何部署surging分布式微服务引擎
- Ubuntu 8.04 Hardy LTS 软件源设置
- 理解 zookeeper
- oracle更改用户名的问题
- 基于SSM的高校后勤管理系统Java项目
- YYText 库学习总结
- SAS2x28扩展卡
- OpenGL绘制框架(Win32版)
- 电脑桌面录制直播嵌入网页
- IDEA新建项目配置tomcat
- 基于SqlServer的DML(数据查询)实验,掌握select查询语句的使用、掌握有无条件查询、结果排序与分组、掌握视图用法
- 恒源云(GPUSHARE)_语音识别与语义处理领域之 NAG 优化器
热门文章
- 英雄联盟胜利因素分析
- HP台式机清灰后无法启动的解决办法
- Python制作个税计算器
- 凸优化理论(一)数学优化问题的分类
- 李少白讲摄影-不放过一切光线 地坛书市新书首发圆满结束
- [No000030]程序员节发点别的:中国教育整个把人脑子搞坏了-易中天
- 蓝牙定位网关-蓝牙网关通过三角定位获取蓝牙设备的位置
- There are 1 missing blocks. The following files may be corrupted:
- python 排列 组合_python实现排列和组合
- 解决raise ValueError(Sample larger than population)问题