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 重构树的性质

  • 根据此构造过程,我们可以得到关于重构树的性质:
  1. 重构树是一棵二叉树,且满足大根堆的性质。
  2. 原图中的 nnn 个点均为重构树中的叶子结点。
  3. 对于点对 (u,v)(u,v)(u,v),它们在原图中的所有路径中,最大边权最小的路径的最大边权为,u,vu,vu,v 在重构树中 lcalcalca 的权值。
  4. 对于一个叶子结点 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加强版)相关推荐

  1. bzoj3551 [ONTAK2010]Peaks加强版 kruskal重构树

    最大边最小,就是最小生成树,可以考虑用主席树维护father数组,但不能遍历子集查找 然后就只能牺牲一部分空间时间来多存一些东西 多存的就是边值大小顺序,本来以为是用主席树排边值,结果由于此题有2种数 ...

  2. bzoj3551: [ONTAK2010]Peaks加强版

    很明显只有最小生成树里面的点有用 我会一个离线的做法,把询问边长排序,逐步合并树,启发式合并splay 在线怎么做呢? 考虑合并出最小生成树的过程,两点合并是并不是一边连向一边而是建出新点,并将新点连 ...

  3. ONTAK2010 Peaks加强版(离线在线)

    题面 弱化版:luogu 强制在线版:bzoj 题解 本题有两种解法 离线算法:线段树合并 先看一道简单题[USACO18JAN]MooTube 本题就是在此基础上求第\(k\)高的点 首先把询问和路 ...

  4. #3551. [ONTAK2010]Peaks加强版(kruskal 重构树 + 主席树)

    #3551. [ONTAK2010]Peaks加强版 我们要求从一个点出发经过困难值小于等于xxx的路径所能到达的山峰中第kkk高的是什么. 考虑按照边权升序,建议kruskalkruskalkrus ...

  5. Kruskal重构树 学习笔记

    Kruskal重构树 学习笔记 文章目录 Kruskal重构树 学习笔记 前言 例题1 BZOJ3732 Network 例题2 [NOI2018] 归程 前言 Kruskal重构树是一种比较冷门的算 ...

  6. [ONTAK2010] Peaks加强版 (kruskal重构树+主席树+倍增)

    Peaks description solution code description 在Bytemountains有N座山峰,每座山峰有他的高度h_i 有些山峰之间有双向道路相连,共M条路径,每条路 ...

  7. kruskal 重构树(讲解 + 例题)

    kruskal重构树 如何建树 模仿kruskalkruskalkruskal,先将所有边排序. 依次遍历每一条边,如果这条边的两个节点(u,vu, vu,v)不在同一个连通块里面, 则新建一个nod ...

  8. kruskal重构树练习

    洛谷 P4197 Peaks 题意: 有 nnn 个山峰,每一个山峰高 hih_ihi​ ,有 mmm 条双向带权边将一些山峰连接起来,有 qqq 次询问,每次询问 (v,x,k)(v,x,k)(v, ...

  9. 【NOI 2018】归程(Kruskal重构树)

    题面在这里就不放了. 同步赛在做这个题的时候,心里有点纠结,很容易想到离线的做法,将边和询问一起按水位线排序,模拟水位下降,维护当前的各个联通块中距离$1$最近的距离,每次遇到询问时输出所在联通块的信 ...

  10. LOJ.2865.[IOI2018]狼人(Kruskal重构树 主席树)

    LOJ 洛谷 这题不就是Peaks(加强版)或者归程么..这算是\(IOI2018\)撞上\(NOI2018\)的题了? \(Kruskal\)重构树(具体是所有点按从小到大/从大到小的顺序,依次加入 ...

最新文章

  1. 未能加载文件或程序集“Report.Basic”或它的某一个依赖项。试图加载格式不正确的程序...
  2. Software caused connection abort: recv failed
  3. Thinkphp3.2学习(一)
  4. 热修复框架Tinker的从0到集成之路(转)
  5. IT规划是文档还是行动?
  6. phpstudy php日志,phpstudy开启网站Apache日志并且按照日期划分创建
  7. torchvision.transforms包的使用
  8. ProtoBuf的介绍以及在Java中使用protobuf将对象进行序列化与反序列化
  9. 基于docker 如何部署surging分布式微服务引擎
  10. Ubuntu 8.04 Hardy LTS 软件源设置
  11. 理解 zookeeper
  12. oracle更改用户名的问题
  13. 基于SSM的高校后勤管理系统Java项目
  14. YYText 库学习总结
  15. SAS2x28扩展卡
  16. OpenGL绘制框架(Win32版)
  17. 电脑桌面录制直播嵌入网页
  18. IDEA新建项目配置tomcat
  19. 基于SqlServer的DML(数据查询)实验,掌握select查询语句的使用、掌握有无条件查询、结果排序与分组、掌握视图用法
  20. 恒源云(GPUSHARE)_语音识别与语义处理领域之 NAG 优化器

热门文章

  1. 英雄联盟胜利因素分析
  2. HP台式机清灰后无法启动的解决办法
  3. Python制作个税计算器
  4. 凸优化理论(一)数学优化问题的分类
  5. 李少白讲摄影-不放过一切光线 地坛书市新书首发圆满结束
  6. [No000030]程序员节发点别的:中国教育整个把人脑子搞坏了-易中天
  7. 蓝牙定位网关-蓝牙网关通过三角定位获取蓝牙设备的位置
  8. There are 1 missing blocks. The following files may be corrupted:
  9. python 排列 组合_python实现排列和组合
  10. 解决raise ValueError(Sample larger than population)问题