1. 例题引入:BZOJ3551

  • 用一道例题引入:BZOJ3551

    题目大意:有 \(N\) 座山峰,每座山峰有他的高度 \(h_i\)。有些山峰之间有双向道路相连,共 \(M\) 条路径,每条路径有一个困难值,这个值越大表示越难走,现在有 \(Q\) 组询问,每组询问询问从点 \(v\) 开始只经过困难值小于等于 \(x\) 的路径所能到达的山峰中第 \(k\) 高的山峰的高度,如果无解输出 \(-1\)。强制在线。

  • 这道题的离线做法可以是线段树合并,可以参照我之前写过的一篇文章,里面有提到:【学习笔记】线段树的扩展(线段树的合并与分裂、可持久化线段树)
  • 强制在线的话,我们似乎没有什么好思路。
  • 先不考虑求第 \(k\) 大的权值,我们先考虑快速判断点对 \((u,v)\) 能否通过边权不超过 \(x\) 的边互相到达。
  • 从最优化的角度考虑,把问题转化为从点 \(u\) 出发,寻找一条 \((u,v)\) 之间的路径,使得这条路径的边权最大值最小。我们需要判断的是这个最小的最大边权是否不超过 \(x\)。
  • 因为无向图形态固定,所以我们要找到一条 \((u,v)\) 之间的路径,使得这条路径的边权最大值最小,实际上就是找到最小生成树中的 \((u,v)\) 之间的路径的最大边权。
  • 然而如果只是查询最大边权写个树上倍增就没了。
  • 这里我们需要访问从点 \(u\) 出发,能到达所有点的集合。很明显,这个集合是一个连通块,在 \(Kruskal\) 算法的过程中,这个连通块必然在某一时刻是完整存在于一个集合的(因为边一定从小到大接进来的),我们利用这一点,可以将点按照它们之间能到达的最大边权进行分类,于是就有了 \(Kruskal\) 重构树。

2. Kruskal 重构树的构造过程

  • 具体做法:

    • 我们把新构建出的图叫做重构树,开始重构树中只有 \(n\) 个孤立的点,我们将它们的点权视作 \(-\infty\)。
    • 在 \(Kruskal\) 算法求最小生成树的过程中,遇到一条连接两个不同集合的边,我们在并查集中分别找到两个集合的根 \(u,v\),新建一个结点 \(w\),合并两个集合,并且令 \(w\) 为新集合的根。
    • 在重构树中将 \(w\) 作为 \(u,v\) 共同的父亲,即在重构树中连边 \(w\to u,w\to v\)。令 \(w\) 的点权为 \((u,v)\) 的边权。

3. Kruskal 重构树的性质

  • 根据此构造过程,我们可以得到关于重构树的性质:
  1. 重构树是一棵二叉树,且满足大根堆的性质。
  2. 原图中的 \(n\) 个点均为重构树中的叶子结点。
  3. 对于点对 \((u,v)\),它们在原图中的所有路径中,最大边权最小的路径的最大边权为,\(u,v\) 在重构树中 \(lca\) 的权值。
  4. 对于一个叶子结点 \(u\),它在原图中经过边权不超过 \(x\) 的边,能到达的点集为:找到一个深度最小的 \(u\) 的祖先 \(v\),使得 \(v\) 的点权不超过 \(x\),根据 \(Kruskal\) 算法的过程和重构树的性质,可以知道,\(v\) 的子树中的叶子结点集合即为能到达的点集。对于一个叶子结点 \(u\),它在原图中经过边权不超过 \(x\) 的边,能到达的点集为:找到一个深度最小的 \(u\) 的祖先 \(v\),使得 \(v\) 的点权不超过 \(x\),根据 \(Kruskal\) 算法的过程和重构树的性质,可以知道,\(v\) 的子树中的叶子结点集合即为能到达的点集。

4. 回到例题:BZOJ3551

  • 那么题目中的询问,我们利用性质4,在重构树中找到深度最小的满足条件的结点 \(u\)。
  • 然后求子树的叶子节点中的 \(k\) 大权值,我们可以转化为 \(dfs\) 序的区间内的 \(k\) 大权值,然后就是经典的静态区间 \(k\) 大问题。
  • 这个只需要对 \(dfs\) 序的每个前缀 \(1\dots i\) 利用主席树(可持久化线段树)维护出权值线段树的每个值域区间的元素个数,查询时候只需要差分一下,在两棵权值线段树上二分即可。
  • 如果不清楚静态区间 \(k\) 大的可以自行百度搜索一下主席树。
  • 总结一下,对于图的形态不变,并且需要限制通过边权小于或大于某个值的边,关于点的连通性的在线查询问题,可以考虑 \(Kruskal\) 重构树。

5. 相关题目

附:例题 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 重构树的更多相关文章

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

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

  2. BZOJ3551 Peaks加强版 [Kruskal重构树,主席树]

    BZOJ 思路 我觉得这题可持久化线段树合并也可以做 我觉得这题建出最小生成树之后动态点分治+线段树也可以做 还是学习一下Kruskal重构树吧-- Kruskal重构树,就是在做最小生成树的时候,如 ...

  3. kruskal重构树学习笔记

    \(kruskal\) 重构树学习笔记 前言 \(8102IONCC\) 中考到了,本蒟蒻不会,所以学一下. 前置知识 \(kruskal​\) 求最小(大)生成树,树上求 \(lca​\). 算法详 ...

  4. Kruskal重构树学习笔记+BZOJ3732 Network

    今天学了Kruskal重构树,似乎很有意思的样子~ 先看题面: BZOJ 题目大意:$n$ 个点 $m$ 条无向边的图,$k$ 个询问,每次询问从 $u$ 到 $v$ 的所有路径中,最长的边的最小值. ...

  5. 算法学习——kruskal重构树

    kruskal重构树是一个比较冷门的数据结构. 其实可以看做一种最小生成树的表现形式. 在普通的kruskal中,如果一条边连接了在2个不同集合中的点的话,我们将合并这2个点所在集合. 而在krusk ...

  6. [luogu P4197] Peaks 解题报告(在线:kruskal重构树+主席树 离线:主席树+线段树合并)

    题目链接: https://www.luogu.org/problemnew/show/P4197 题目: 在Bytemountains有N座山峰,每座山峰有他的高度$h_i$.有些山峰之间有双向道路 ...

  7. [算法模板]Kruskal重构树

    [算法模板]Kruskal重构树 kruskal重构树是一个很常用的图论算法.主要用于解决u->v所有路径上最长边的最小值,就是找到\(u->v\)的一条路径,使路径上的最长边最小. 图片 ...

  8. Kruskal重构树——[NOI2018] 归程

    题目链接: UOJ LOJ 感觉 Kruskal 重构树比较简单,就不单独开学习笔记了. Statement 给定一个 \(n\) 点 \(m\) 边的无向连通图,用 \(l,a\) 描述一条边的长度 ...

  9. 【BZOJ】3732: Network【Kruskal重构树】

    3732: Network Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2812  Solved: 1363[Submit][Status][Dis ...

随机推荐

  1. javaSE总结(二)--java面向对象

    一.类和对象 (1)类 [修饰符] class 类名{ //修饰符1:private public protected 三个最多出现其一 //修饰符2:abstract final 两个最多出现其一 ...

  2. 用easyui实现查询条件的后端传递并自动刷新表格的两种方法

    用easyui实现查询条件的后端传递并自动刷新表格的两种方法 搜索框如下: 通过datagrid的load方法直接传递参数并自动刷新表格 通过ajax的post函数传递参数并通过loadData方法将 ...

  3. php 函数篇

    1.array_values($data); 注:将关联数组转化为索引数组 <?php $a=array("Name"=>"Bill"," ...

  4. Vue的router-link标签

    在vue1.0版本的超链接标签还是原来的a标签,链接地址由v-link属性控制 而vue2.0版本里超链接标签由a标签被替换成了router-link标签,但最终在页面还是会被渲染成a标签的 至于为什 ...

  5. oracle基础知识语法大全

    ORACLE支持五种类型的完整性约束NOT NULL (非空)--防止NULL值进入指定的列,在单列基础上定义,默认情况下,ORACLE允许在任何列中有NULL值.CHECK (检查)--检查在约束中 ...

  6. CentOS -- 新建用户并使能密钥登录

    目录 1. 新建用户 2. 为新用户授权 2.1. 方法一:把新用户添加到wheel用户组中 2.2. 方法二:把新用户添加到sudoers列表中 3. 新用户使能 SSH 密钥登录 4. 其它 4. ...

  7. 解决SqlDataSource连接超时的问题

    采用两种策略: 1.连接字符串增加Connect Timeout=1000(大约1000秒/60=16分钟) 2.设置SqlDataSourced 的 EnableCaching="True ...

  8. CRC-CCITT CRC-16

    CRC分为以下几种标准: CRC-12码 CRC-16码 CRC-CCITT码 CRC-32码 在线CRC计算器 https://www.lammertbies.nl/comm/info/crc-ca ...

  9. iOS学习——NSLog输出各种类型

    在开发过程中,在调试过程中经常打印不出自己想要的数据格式,还时常报警告,所以整理了一下iOS中用NSLog打印各种数据类型的样式.整型占位符说明 : %d : 十进制整数, 正数无符号, 负数有 “- ...

  10. vsftp 常见配置测试与故障排除

    匿名用户 /var/ftp        本地用户 /home/username配置vsftpd时,强烈建议·# cp /etc/vsftpd.conf /etc/vsftpd.conf1       ...