Description

给定一个 \(n\) 个点,\(m\) 条边的无向连通图,边带权。

\(q\) 次询问,每次询问两个点 \(x, y\),求两点间的次小瓶颈路。不存在输出 -1

Hint

  • \(1\le n\le 10^5\)
  • \(n-1\le m\le 2\times 10^5\)
  • \(1\le q\le 2\times 10^5\)

Solution

若是求最小瓶颈路的话我们会有一个这样的思路:

由于 最小瓶颈路必定在这个图的最小生成树(MST)上,因此我们可以建出 Kruskal 重构树

何为 Kruskal 重构树?我们回忆一下 Kruskal 求最小生成树的算法,是选取一个跨越两个连通块的边 \((u, v)\) 进行连接。但现在我们不连边,而书 新建一个结点 \(x\),从 \(x\) 向 \(u, v\) 各连一条边。每一个新建的结点都带有一个 点权权值大小即为对应边的边权。最终得到的 \(2n-1\) 个结点的树形结构,即为 Kruskal 重构树。

如果我们深入挖掘这棵树的性质,不难发现这些新建点必然对应着 MST 中的边。于是 \(x\) 到 \(y\) 的瓶颈路一定是重构树上的一条路径。而我们又可以看出,祖先结点的加入时间是晚于子孙的,因此 祖先的点权必然大于其子孙。最后,不难得到结论——\(x, y\) 两点的瓶颈路即为两点在重构树上的 \(LCA\) 的点权

以上就是最小瓶颈路的求法。


对于次小瓶颈路我们可以稍加拓展。

既为『次小』,那么必然是有两条。

注意一下这里,我们需要对上述的建树方法做一点改动,原本发现是同一个连通块是我们会直接舍弃这条边。然而我们求次小的,这条边并不一定毫无贡献。直接往所在的连通块连接即可。

由于说明需要,这个重构树还有一个比较显然的性质——一个重构树上的子树,若树根 \(x\) 点权为 \(w\),那么这棵子树对应一个连通块,块内所有点可以通过权值不超过 \(w\) 的边可以互相到达。

于是对于两个询问点 \(x, y\),我们只要找到重构树上这样一个点 \(z\),满足 \(z\) 是两点的祖先,并且其对应连通块存在次小瓶颈路。在建重构树的过程中,我们通过一个标记 \(type\) 来表示子树所对应的连通块是否存在。

对于一个链状的连通块,很显然只有一条路径。

根据这一点,我们可以设计出如下判断条件:

  • 若新建点 \(x\) 只连向了一个点,说明该连通块中 存在环 了,那么 \(type = 1\)。
  • 若连向了两个连通块:
    • 如果 两个连通块存在一个非链状块,那么合并后显然也是,\(type = 1\)。
    • 如果加上这条边后,发现一个 度数超过 \(2\) 的顶点,那么同样为非链块,\(type = 1\)。
    • 否则 \(type = 0\)。

处理询问的时候,我们先其求出 \(LCA\),然后 倍增 地往上跳祖先,直到找到一个 深度最大 \(type = 1\) 的点。其点权即为答案。

这个算法的时间复杂度为 \(O((n+m+q)\log (n+m))\)。

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : APIO2020 交换城市
*/
#include "swap.h" #include <algorithm>
#include <vector> using namespace std;
const int M = 2e5 + 5;
const int N = 1e5 + 5; int n, m;
struct Edge {
int u, v, w;
} e[M]; const int V = N + M;
const int LogV = 20; vector<int> tr[V];
int fa[V][LogV];
int dep[V];
int val[V], ind[V];
bool type[V];
int tot = 0; int uset[V];
int find(int x) {
return x == uset[x] ? x : uset[x] = find(uset[x]);
} void caldep(int x) {
dep[x] = dep[fa[x][0]] + 1;
for (auto y : tr[x]) caldep(y);
} void Kruskal() {
for (int i = 1; i <= n; i++) uset[i] = i;
sort(e + 1, e + m + 1, [&](Edge a, Edge b) {
return a.w < b.w;
}); tot = n;
for (int i = 1; i <= m; i++) {
int u = e[i].u, fu = find(u);
int v = e[i].v, fv = find(v);
int w = e[i].w; ++tot;
if (fu == fv) {
val[tot] = w;
fa[fu][0] = tot;
uset[fu] = uset[tot] = tot;
type[tot] = true;
tr[tot].push_back(fu);
} else {
val[tot] = w;
fa[fu][0] = fa[fv][0] = tot;
uset[fu] = uset[fv] = uset[tot] = tot;
if (++ind[u] > 2 || ++ind[v] > 2) type[tot] = true;
if (type[fu] || type[fv]) type[tot] = true;
tr[tot].push_back(fu);
tr[tot].push_back(fv);
}
}
type[0] = 1; for (int j = 1; j < LogV; j++)
for (int i = 1; i <= tot; i++)
fa[i][j] = fa[fa[i][j - 1]][j - 1]; dep[0] = 0;
caldep(tot);
} int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = LogV - 1; ~i; --i)
if (dep[x] - (1 << i) >= dep[y])
x = fa[x][i];
if (x == y) return x;
for (int i = LogV - 1; ~i; --i)
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
} void init(int tmp_n, int tmp_m, vector<int> U, vector<int> V, vector<int> W) {
n = tmp_n, m = tmp_m;
for (int i = 0; i < m; i++)
e[i + 1] = Edge{U[i] + 1, V[i] + 1, W[i]};
Kruskal();
} int getMinimumFuelCapacity(int x, int y) {
int z = lca(++x, ++y);
if (type[z]) return val[z]; for (int j = LogV - 1; ~j; --j)
if (!type[fa[z][j]])
z = fa[z][j];
z = fa[z][0]; return z ? val[z] : -1;
}

【APIO2020】交换城市(Kruskal重构树)的更多相关文章

  1. BZOJ5415[Noi2018]归程——kruskal重构树+倍增+堆优化dijkstra

    题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 n 个节点.m 条边的无向连通图(节点的编号从 1 至 n).我们依次用 l,a 描述一条边的长度.海 ...

  2. [SCOI2013]摩托车交易 kruskal重构树(最大生成树) 倍增

    ---题面--- 题解: 这题想法简单,,,写起来真的是失智,找了几个小时的错误结果是inf没开到LL范围.... 首先我们需要找到任意两点之间能够携带黄金的上限值,因为是在经过的道路权值中取min, ...

  3. NOI2018归程(Kruskal重构树)

    题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 n 个节点.m 条边的无向连通图(节点的编号从 1 至 n). 我们依次用 l,a 描述一条边的长度. ...

  4. Luogu P4768 [NOI2018]归程(Dijkstra+Kruskal重构树)

    P4768 [NOI2018]归程 题面 题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 \(n\) 个节点. \(m\) 条边的无向连通图(节点的编 ...

  5. Luogu P1967 货车运输(Kruskal重构树)

    P1967 货车运输 题面 题目描述 \(A\) 国有 \(n\) 座城市,编号从 \(1\) 到 \(n\) ,城市之间有 \(m\) 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 \ ...

  6. NOIP 2013 提高组 洛谷P1967 货车运输 (Kruskal重构树)

    题目: A 国有 nn 座城市,编号从 11 到 nn,城市之间有 mm 条双向道路.每一条道路对车辆都有重量限制,简称限重. 现在有 qq 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情 ...

  7. [bzoj 3732] Network (Kruskal重构树)

    kruskal重构树 Description 给你N个点的无向图 (1 <= N <= 15,000),记为:1-N. 图中有M条边 (1 <= M <= 30,000) ,第 ...

  8. 【BZOJ 3732】 Network Kruskal重构树+倍增LCA

    Kruskal重构树裸题, Sunshine互测的A题就是Kruskal重构树,我通过互测了解到了这个神奇的东西... 理解起来应该没什么难度吧,但是我的Peaks连WA,,, 省选估计要滚粗了TwT ...

  9. 【BZOJ-3545&3551】Peaks&加强版 Kruskal重构树 + 主席树 + DFS序 + 倍增

    3545: [ONTAK2010]Peaks Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1202  Solved: 321[Submit][Sta ...

随机推荐

  1. Java基础 之二 类和对象

    1.基础概念 1) 概念 简单来说,类是具有相同特征事物的抽象,比如有轮子的都可以抽象为车:对象则可以看做类的具体实例,比如创建一个法拉利的车,就是车这个类的实例. 抽象.封装.继承.多态是类的四个特 ...

  2. 数据结构实训——哈夫曼(Huffman)编/译码器

    题目4.哈夫曼(Huffman)编/译码器(限1人完成) [问题描述] 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本.但是,这要求在发送端通过一个编码系统对待传数据预先 ...

  3. VScode,code::blocksC语言编译运行出现不支持的16位应用程序解决方法

    最近,莫名其妙c代码就是编译运行不了,老是提示不支持的16位应用程序 然后网上找了各种教程 只有这个成功了(害得我还升了下系统) 实现进入Windows设置 然后点更新和安全--恢复 然后点高级启动- ...

  4. Java web项目JXl导出excel,(从eclipse上移动到tomact服务器上,之路径更改)

    我用的是jxl导出excel,比较简单,最开始我是固定路径不能选择,很局限,后来改了,而且固定路径当把项目放在服务器上时,路径不可行. 在网上各位大神的帮助成功设置响应头,并且可选保存路径. 1.前端 ...

  5. [C/C++] 结构体内存对齐:alignas alignof pack

    简述: alignas(x):指定结构体内某个成员的对齐字节数,指定的对齐字节数不能小于它原本的字节数,且为2^n; #pragma pack(x):指定结构体的对齐方式,只能缩小结构体的对齐数,且为 ...

  6. c# 调用Go 动态库

    [StructLayout(LayoutKind.Sequential)] public struct GoMem { public IntPtr data; public UInt64 len; p ...

  7. FL Studio杂项设置页讲解(上)

    今天我们来看一下FL Studio通道设置窗口中的杂项设置页面.该页面存在于FL Studio绝大多数的通道中,我们可以通过它来设置一些发生器或者第三方插件的参数,接下来就让我们一起来学习下这些参数的 ...

  8. 用MindManager画思维导图的好处有哪些?

    大家都听说过思维导图吧?有没有将这样一种图形思维工具真正运用到我们的日常生活中去呢?是否真的知道思维导图怎么用呢?今天小编就来给大家讲一讲,思维导图怎么用. 老规矩,先给大家讲一讲小编用的软件跟系统, ...

  9. U盘数据丢失怎么办,还能恢复吗

    有时候在用U盘的时候会出现数据丢失或者U盘无法打开的问题,检查过之后,发现U盘格式变成了RAW,这是怎么回事?遇到这种情况该怎么解决呢? 首先来看看造成u盘格式变为RAW的主要原因: 1.非正常退出u ...

  10. JS 数组对象

    定义数组: 数组对象用来在单独的变量名中存储一系列的值. 创建一个数组有三种方法. 1: 常规方式: var myCars=new Array(); myCars[0]="Saab" ...