首页 > 其他分享 >Kruskal重构树 学习笔记

Kruskal重构树 学习笔记

时间:2022-10-16 23:56:08浏览次数:79  
标签:重构 原图 Kruskal 笔记 节点 生成 边权

我们回顾一下最小与最大生成树的性质:

对于一张图的最小生成树,原图中任意两个节点中任意一条路径的边权最大值的最小值为生成树中节点路径间边权的最大值。最大生成树则相反,原图中任意两个节点中任意一条路径的边权最小值的最大值为生成树中节点路径间边权的最小值。

以下以最小生成树为例,最大生成树则同理。

回顾一下 \(\text{Kruskal}\) 算法求最小生成树的过程:先将所有的边权排序,再通过并查集判断两个点是否已经在同一颗树上,若没有则相连。我们换一种建树方式:同样是先将所有的边权排序,也同样通过并查集判断,但在连边的时候,我们不是直接将两个点相连,而是新建一个节点连向这两个点在并查集中的祖先,且新节点的点权为原图两点的边权(此时这个新节点就不是原图中的节点了)。完毕后构成的一棵树即为原图的 \(\text{Kruskal}\) 重构树。\(\text{Kruskal}\) 重构树有以下性质:

  1. 是一颗二叉树;
  2. 若原本需要生成的是最生成树,则重构树是一个根堆,最大生成树则相反;
  3. 原图中任意两点在重构树上的 \(\text{LCA}\) 的点权即为该两点在原图中经过的路径边权最大值的最小值,即在最小生成树中经过的路径边权最大值。这意味着对于一个权为 \(x\) 的节点,其子树中的任意两个节点都可以在原图(或最小生成树)中通过权都小于等于 \(x\) 的边到达。

结合图理解:

图1

该图的最小生成树是:

图2

该图的 \(\text{Kruskal}\) 重构树是:

图3

节点旁边的数值为该点的点权。\(\text{Kruskal}\) 重构树的结构有点类似于哈夫曼树。其叶节点都是原图中的节点,其余节点为求最小生成树时建立重构树所产生的新节点。善用 \(\text{Kruskal}\) 重构树的性质可以解决许多类型的题目。

例题:P4197 Peaks

题目大意:给定一张 \(n\) 个点、\(m\) 条边的无向图,点有点权,边有边权,有 \(q\) 个询问,每次询问对于一个节点,经过所有边权小于等于 \(x\) 的边所能到达的节点中第 \(k\) 高的点权,无解输出 \(-1\)。

思路:容易想到对原图建立 \(\text{Kruskal}\) 重构树。对于每一个节点,我们维护动态开点的权值线段树(需要离散化)记录其子树中的所有点权,到祖先时合并左右两节点的线段树到自身即可(易知重构树中的节点要么是叶节点,要么有两个子节点)。我们可以提前处理出倍增数组,对于每一个询问给出的点 \(u\) ,我们向上倍增,找到 \(u\) 的祖先中深度最浅的、点权小于等于 \(x\) 的节点,它的子树中的所有叶节点即为在原图中可以通过所有权都小于等于 \(x\) 的边所能到达的节点(由上面的性质可知)。找到这个节点后先判断无解,若有解再线段树上二分即可。由于是在线算法,所以也可以处理 P7834 [ONTAK2010] Peaks 加强版 中的强制在线操作。

P4197 AC Code:

#include <bits/stdc++.h>
#define mid (l + r >> 1)

using namespace std;

struct E {
    int u, v, w;
    
    const bool operator<(const E &rhs) const {
        return w < rhs.w;
    }
};

int n, m, u, v, w, gr, cnt, macnt = 1, tot, q, f[200005], fa[200005][20], h[100005], p[200005], b[100005], r[100005], rt[200005], sum[40000005], ls[40000005], rs[40000005];
E e[500005];
unordered_map<int, int> ma;

int fgr(int u, int x) {
    for (int i = 19; ~i && p[fa[u][0]] <= x; --i)
        if (p[fa[u][i]] <= x)
            u = fa[u][i];
    
    return u;
}

int find(int x) {
    return x == f[x]? x: f[x] = find(f[x]);
}

void merge(int l, int r, int &x, int y, int z) {
    if (!y) { x = z; return; }
    if (!z) { x = y; return; }
    
    sum[x = ++tot] = sum[y] + sum[z];
    
    if (l < r) {
        merge(l, mid, ls[x], ls[y], ls[z]);
        merge(mid + 1, r, rs[x], rs[y], rs[z]);
    }
}

void ExKruskal() {
    cnt = n;
    sort(e + 1, e + m + 1);
    
    for (int i = 1; i <= m; ++i)
        if ((u = find(e[i].u)) != (v = find(e[i].v))) {
            f[u] = f[v] = ++cnt;
            f[cnt] = cnt;
            p[cnt] = e[i].w;
            fa[u][0] = fa[v][0] = cnt;
            merge(1, macnt, rt[cnt], rt[u], rt[v]);
        }
}

void add(int l, int r, int &x, int tar) {
    x = ++tot;
    
    if (l == r) {
        ++sum[x];
        
        return;
    }
    
    if (tar <= mid) add(l, mid, ls[x], tar);
    else add(mid + 1, r, rs[x], tar);
    
    sum[x] = sum[ls[x]] + sum[rs[x]];
}

int query(int l, int r, int x, int k) {
    if (l == r) return l;
    
    if (sum[ls[x]] >= k)
        return query(l, mid, ls[x], k);
    else
        return query(mid + 1, r, rs[x], k - sum[ls[x]]);
}

int main() {
    p[0] = 2e9;
    scanf("%d %d %d", &n, &m, &q);
    
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &h[i]);
        f[i] = i, b[i] = h[i];
    }
    
    sort(b + 1, b + n + 1);
    r[1] = b[1];
    ma[b[1]] = 1;
    
    for (int i = 2; i <= n; ++i)
        if (b[i] != b[i - 1]) {
            r[++macnt] = b[i];
            ma[b[i]] = macnt;
        }
    
    for (int i = 1; i <= n; ++i)
        add(1, macnt, rt[i], ma[h[i]]);
    
    for (int i = 1; i <= m; ++i) {
        scanf("%d %d %d", &u, &v, &w);
        e[i].u = u, e[i].v = v, e[i].w = w;
    }
    
    ExKruskal();
    
    for (int i = 1; i < 20; ++i)
        for (int j = 1; j <= cnt; ++j)
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
    
    while (q--) {
        scanf("%d %d %d", &u, &v, &w);
        gr = fgr(u, v);
        
        if (sum[rt[gr]] < w)
            puts("-1");
        else
            printf("%d\n", r[query(1, macnt, rt[gr], sum[rt[gr]] - w + 1)]);
    }
    
    return 0;
}

标签:重构,原图,Kruskal,笔记,节点,生成,边权
From: https://www.cnblogs.com/wf715/p/kruskal-chonggoushu.html

相关文章

  • 树上莫队 学习笔记
    树上莫队本质上是把树上的结点转化为区间信息,从而使用莫队求解。但是不能直接使用树链剖分的\(\text{dfs}\)序,因为树上任意一条路径所对应的区间不是连续的。此处需要用......
  • oracle学习笔记
    select*fromtest_all;--全量的数据insertintotest_all(ID,NAME,FISRT_FLG)values('1','aaa','1');insertintotest_all(ID,NAME,FISRT_FLG)values......
  • 自适应辛普森法 学习笔记
    对于一个二次函数\(f(x)=ax^2+bx+c\),积分得\(F(x)=\displaystyle\int_0^xf(t)\,\mathrm{d}t=\dfrac{a}{3}x^3+\dfrac{b}{2}x^2+cx+C\)。于是\[\dis......
  • acm训练笔记
    2022.10.16模拟赛2019ChinaCollegiateProgrammingContestFinal(CCPC-Final2019)L题意:只能直行或者右转,问有多少种走遍一个矩阵的方法。解法:构造题,考虑任意一种......
  • Flask学习笔记(十三)-Flask_WTF实现表单
    一、Web表单Web表单是Web应用程序的基本功能。它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页......
  • SpringCloud学习笔记(三)——Ribbon
    一、restTemplate的使用我们直接通过实例来说明和理解。首先新建一个子模块,用来测试restTemplate的使用  在测试的主类中添加如下代码,我们就能够获取百度界面的htm......
  • 系统分析师学习笔记(8)-图论与图示网络的最大流量
    要找出图示的最大流量:1.找出最大运量的路径,该路径的最小值为瓶颈值,抽取该值;2.在找出的路径减去抽取值,为0的路径取消;3.在剩余的路径中,找出最大的抽取值,重复步骤1&2;4.将各个步......
  • 20201302姬正坤Linux第四章学习笔记
    第四章并发编程一、并行计算导论1、顺序算法与并行算法在描述顺序算法中,常用一个begin-end代码块列出算法。该代码块中的所有步骤都是通过某个任务依次执行的。而并行......
  • Java核心技术阅读笔记(第五章)
    Chapter5继承作者:Denis版本:1.0编写时间:2022/10/16编写地点:中国山西省5.1类、超类和子类如果一个类继承自另一个类,那么这个类被称为子类,被继承的类被称为超类......
  • 第四单元读书笔记
    第四章并发编程介绍Pthread中的线程操作,包括线程管理函数,互斥量、连接、条件变量和屏障等线程同步工具。4.1并行计算导论4.1.1顺序算法与并序算法使用cobegin-c......