网上感觉没有什么很详细 + 证明的讲解啊)

前置:Kruskal 求最小生成树。

这个算法可以将一棵树 / 无向连通图重构成一颗有性质的新树。

算法可以解决一些树上瓶颈边权之类的问题,可以把需要持久化的并查集给代替掉。

设 \(f_i\) 为 \(i\) 所在联通块的根。

算法流程和 Kruskal 最小生成树的过程非常类似:

  1. 将所有边按边权从小到大排序
  2. 顺序遍历每条边 \((u, v, w)\),若 \(u, v\) 已经联通跳过,否则建立一个新点 \(x\),让 \(x\) 作为 \(f_u\) 与 \(f_v\) 的父亲(即连 \(x \Rightarrow f_u\) 和 \(x \Rightarrow f_v\) 的有向边),然后让 \(f_u = f_v = x\)。这个新点的点权是 \(w\)。

时间复杂度 \(O(m \log m + n \log n)\)。

最后,以最后一个建立的新点作为 \(rt\) ,就是一颗重构树了(下面是一个无向图联通变成重构树的例子,排序后第 \(i\) 条边的编号是 \(n + i\),点权是红色,蓝色是新点,黑色是原来的点)。

这棵树有如下性质:

  • 原树若有 \(n\) 个节点,那么新树有 \(2n - 1\) 个节点,根是 \(2n - 1\)。因为建的新点就是合并两个点的次数,合并 \(n - 1\) 次。最后一次合并作为根,凑成了整个树。
  • 所有原来的点就是叶子节点。因为建新图过程中我们没有让原来的点当父亲。
  • 对于任意的 \(x\) 点,它的祖先链从下往上点权都是非严格递增的。因为每次合并的时候,只有 \(\le w\) 的边都构造好了,所以此时 \(f_u\) 的点权也 \(\le w\)。
  • 重构树的点权是一个大根堆。跟上一个性质的等价的。
  • 对于一个 \(x\) 和一个值 \(v\)。从 \(x\) 出发只经过 \(\le v\) 的边能到达的点集 \(=\) \(x\) 的祖先节点中深度最小的点权 \(\le v\) 的点 \(z\) 的子树中的原来的点集。(证明:这颗子树外的点显然不行,因为再往上点权 \(> v\),说明再往上其他的点使通过 \(> v\) 的边才和 \(x\) 点连上的,所以不行;这颗子树内的点显然可以,因为这是一个大根堆,所以子树内的点都可以用 \(\le v\) 的边互相可达,他们在新树上的路径,经过的所有编号就是原树上经过的所有边。从这个角度,我们其实可以看作这个重构树以子树包含的形式等价于储存了 Kruscal 任何时间戳的版本。
  • 对于任意 \(x, y\) ,其最小瓶颈边权(使其最大边最小的路径的最大边)为 \(x, y\) 在新树上的 LCA 点权。\(x, y\) 在经过 LCA 这条边后恰好联通,由于从小到大顺序执行,说明这条边是路径上最大的边。

如果求最大生成树,反着排序,那么偏序关系都反转,就不赘述了。

为了方便我自己创了一个名词,如果从小到大排序形成的大根堆叫 Kruscal 最小重构树,反之叫 Kruscal 最大重构树。

例题

[NOI2018]归程

预处理 \(d_i\) 表示从 \(i\) 到 \(1\) 的最短路径,这个反着建边跑最短路就行了。

问题变为:每个点有个权值,每个询问是从 \(v\) 出发经过权值 \(> p\) 的边能到的点的最小值,强制在线。

如果可以离线,那么从大到小排序边权,然后执行 Kruscal,维护一下每个联通块的最小值,每次在尝试完 merge \(>p\) 的所有边后,对应 \(O(1)\) 查询就可以了。

强制在线的话,可持久化并查集是 \(O((n + q) \log ^2 n)\) 的,是可以 的。

用 Kruscal 重构树的话,从大到小排序边权建 Kruscal 最大重构树,那么从 \(v\) 出发经过 \(> p\) 的边能到的点 \(=\) \(v\) 的祖先中深度最小的满足点权 \(> p\) 的点 \(x\) 的子树中所有原来的点。

由于有单调性,倍增跳就好了,子树点权最小,预处理一下就好了。

复杂度 \(O(m \log m +(n + q) \log n)\)

Code

链接

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. #include <algorithm>
  6. #include <vector>
  7. using namespace std;
  8. typedef pair<int, int> PII;
  9. const int N = 200005, M = 400005, INF = 2e9, L = 19;
  10. int n, m, Q, K, S, lastans, d[N], f[N << 1], w[N << 1], val[N << 1], cnt, fa[N << 1][L];
  11. int head[N], numE = 0;
  12. bool vis[N];
  13. priority_queue<PII, vector<PII>, greater<PII> > q;
  14. struct E {
  15. int next, v, w;
  16. } e[M << 1];
  17. vector<int> g[N << 1];
  18. struct Edge {
  19. int u, v, w;
  20. bool operator<(const Edge &b) const { return w > b.w; }
  21. } b[M];
  22. void inline add(int u, int v, int w) {
  23. e[++numE] = (E){ head[u], v, w };
  24. head[u] = numE;
  25. }
  26. void inline clear() {
  27. memset(head, 0, sizeof head);
  28. memset(fa, 0, sizeof fa);
  29. numE = lastans = 0;
  30. for (int i = 1; i < 2 * n; i++) g[i].clear();
  31. }
  32. void inline dijkstra() {
  33. for (int i = 1; i <= n; i++) d[i] = INF, vis[i] = false;
  34. q.push(make_pair(d[1] = 0, 1));
  35. while (!q.empty()) {
  36. PII u = q.top();
  37. q.pop();
  38. if (vis[u.second])
  39. continue;
  40. vis[u.second] = true;
  41. for (int i = head[u.second]; i; i = e[i].next) {
  42. int v = e[i].v;
  43. if (d[u.second] + e[i].w < d[v]) {
  44. d[v] = d[u.second] + e[i].w;
  45. q.push(make_pair(d[v], v));
  46. }
  47. }
  48. }
  49. }
  50. int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
  51. void inline kruscal() {
  52. sort(b + 1, b + 1 + m);
  53. for (int i = 1; i < 2 * n; i++) f[i] = i;
  54. for (int i = 1; i <= m; i++) {
  55. int u = find(b[i].u), v = find(b[i].v);
  56. if (u == v)
  57. continue;
  58. ++cnt;
  59. g[cnt].push_back(u), g[cnt].push_back(v);
  60. f[u] = f[v] = cnt, w[cnt] = b[i].w;
  61. }
  62. }
  63. void dfs(int u) {
  64. val[u] = u <= n ? d[u] : INF;
  65. for (int i = 1; i < L && fa[u][i - 1]; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
  66. for (int i = 0; i < g[u].size(); i++) {
  67. int v = g[u][i];
  68. if (v == fa[u][0])
  69. continue;
  70. fa[v][0] = u;
  71. dfs(v);
  72. val[u] = min(val[u], val[v]);
  73. }
  74. }
  75. int main() {
  76. freopen("return.in", "r", stdin);
  77. freopen("return.out", "w", stdout);
  78. int T;
  79. scanf("%d", &T);
  80. while (T--) {
  81. scanf("%d%d", &n, &m);
  82. cnt = n;
  83. for (int i = 1, u, v, l, a; i <= m; i++) {
  84. scanf("%d%d%d%d", &u, &v, &l, &a);
  85. add(u, v, l), add(v, u, l);
  86. b[i] = (Edge){ u, v, a };
  87. }
  88. dijkstra();
  89. kruscal();
  90. dfs(2 * n - 1);
  91. scanf("%d%d%d", &Q, &K, &S);
  92. while (Q--) {
  93. int v, p;
  94. scanf("%d%d", &v, &p);
  95. v = (v + K * lastans - 1) % n + 1;
  96. p = (p + K * lastans) % (S + 1);
  97. for (int i = L - 1; ~i; i--)
  98. if (fa[v][i] && w[fa[v][i]] > p)
  99. v = fa[v][i];
  100. printf("%d\n", lastans = val[v]);
  101. }
  102. if (T)
  103. clear();
  104. }
  105. return 0;
  106. }

学习笔记:Kruscal 重构树的更多相关文章

  1. [学习笔记]kruskal重构树 && 并查集重构树

    Kruskal 重构树 [您有新的未分配科技点][BZOJ3545&BZOJ3551]克鲁斯卡尔重构树 kruskal是一个性质优秀的算法 加入的边是越来越劣的 科学家们借这个特点尝试搞一点事 ...

  2. [NOI2018]归程(kruscal重构树)

    [NOI2018]归程 题面太长辣,戳这里 模拟赛上写了一个spfa (关于spfa,它已经死了),然后一个st表水完暴力跑路.考后说是Kruscal重构树或者可持久化并查集???这都是些什么东西.不 ...

  3. 『Kruscal重构树 Exkruscal』

    新增一道例题及讲解 Exkruscal \(Exkruscal\)又称\(Kruscal\)重构树,是一种利用经典算法\(Kruscal\)来实现的构造算法,可以将一张无向图重构为一棵具有\(2n-1 ...

  4. loj2876 水壶 [JOISC 2014 Day2] kruscal重构树

    正解:kruscal重构树+bfs 解题报告: 我永远喜欢loj! 感觉这题和这题挺像的,,,预处理和解题方法都是,,,所以大概整体二分能过去? 但因为做这题主要是入门一下kruscal重构树,,,所 ...

  5. kruscal重构树略解

    我们先看一道题:Luogu P4197 Peaks 这道题珂以用启发式合并+主席树来做 那么强制在线呢?(bzoj 3551 [ONTAK2010]Peaks加强版) 离线做法就不行了 我们就要用一个 ...

  6. BZOJ_3545_[ONTAK2010]Peaks_主席树+倍增+kruscal重构树+dfs序

    BZOJ_3545_[ONTAK2010]Peaks_主席树+倍增+kruscal重构树 Description 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道 ...

  7. $ CometOJ-Contest\#11\ D$ $Kruscal$重构树

    正解:$Kruscal$重构树 解题报告: 传送门$QwQ$ 发现一个图上搞就很麻烦,考虑变为生成树达到原有效果. 因为在询问的时候是要求走到的点编号尽量小,发现这个时候点的编号就成为限制了,于是不难 ...

  8. 洛谷$P4768\ [NOI2018]$归程 $kruscal$重构树

    正解:$kruscal$重构树 解题报告: 传送门$QwQ$ 语文不好选手没有人权$TT$连题目都看不懂真的要哭了$kk$ 所以先放个题目大意?就说给定一个$n$个点,$m$条边的图,每条边有长度和海 ...

  9. 【题解】洛谷P1967 [NOIP2013TG] 货车运输(LCA+kruscal重构树)

    洛谷P1967:https://www.luogu.org/problemnew/show/P1967 思路 感觉2013年D1T3并不是非常难 但是蒟蒻还是WA了一次 从题目描述中看出每个点之间有许 ...

随机推荐

  1. http 请求体数据--ngx

    HTTP包体的长度有可能非常大,不同业务可能对包体读取 处理不相同, 比如waf, 也许会读取body内容或者只是读取很少的前几十字节.所以根据不同业务特性,对http body 数据包处理方式不同, ...

  2. kafka生产者数据可靠性保证

    为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到 producer 发送的数据后,都需要向 producer 发送 ack(ackn ...

  3. 给你一条sql语句如何进行优化

    我们sql语句的书写是根据业务逻辑进行书写的,如果执行比较慢,那么我们对sql重写: 如分步查询,然后在代码层进行拼接:用临时表:改变sql语句的写法等等.我们称之为逻辑层优化. 然后我们看看每条sq ...

  4. ceph单机多mon的实现

    ceph默认情况下是以主机名来作为mon的识别的,所以这个情况下用部署工具是无法创建多个mon的,这个地方使用手动的方式可以很方便的创建多个mon 1.创建mon的数据存储目录 mkdir /var/ ...

  5. arm-linux校时和时钟同步

    # 将时间写到系统 date 2020.08.25-14:02:00 # 将时间同步到硬件时钟芯片 hwclock -f /dev/rtc1 -w # 将时间从硬件时钟芯片同步到系统 hwclock ...

  6. time和random模块

    # 时间模块 # 三种时间表现形式 # 时间戳 # 格式化的时间字符串 # 元组(struct_time)结构化时间 struct_time元组共有9个元素(年,月,日,时,分,秒,一年中的第几周,一 ...

  7. [LeetCode题解]143. 重排链表 | 快慢指针 + 反转

    解题思路 找到右边链表,再反转右边链表,然后按左.右逐一合并 代码 /** * Definition for singly-linked list. * public class ListNode { ...

  8. go返回json数据

    package main import ( "encoding/json" ) type Repay struct { Code uint64 `json:"code&q ...

  9. 学习Validator验证框架总结

    在项目开发中许多地方需要加以验证,对于使用if-else简单粗暴一个一个验证,spring的validation封装了Javax ValidationI校验参数,大大缩减了代码量. 以前的分层验证,从 ...

  10. 思维导图哪款好用?怎么借助MindManager 做旅游计划

    世界那么大,想不想去看看!想不想来一场说走就走的旅行?尤其是在新冠的笼罩下, 2020年已经过去四分之三,国内疫情已经基本得到了控制,接下来的日子里你想出门好好玩玩吗? 说走就走的旅游虽然美好,但是你 ...