kruskal重构树学习笔记
\(kruskal\) 重构树学习笔记
前言
\(8102IONCC\) 中考到了,本蒟蒻不会,所以学一下。
前置知识
\(kruskal\) 求最小(大)生成树,树上求 \(lca\)。
算法详解
\(kruskal\) 重构树可以解决瓶颈路问题(如:\(noip2013\) \(d1t3\) 货车运输,可以当做模板题来做,本文中也将此题作为例题);
我们来思考一下 \(kruskal\) 求最小(大)生成树的过程(后文中以最大生成树为例),大致过程可以概述为:将图中所有边从大到小排序,枚举,如果该边左右两端的点不在同一个联通块里就连起来,即:该边在最大生成树上。(其中联通块用 \(dsu\) 维护)
\(kruskal\) 重构树就是把最大生成树上的边建成树,实现过程:在 \(kruskal\) 算法进行过程中,对于每次要连接的两个联通块 \(A\) 和 \(B\),用一个新的节点当做 \(A\) \(B\) 在重构树中的父亲节点,并把新节点的点权设为连接联通块的边的边权,以此操作来代替连边操作,联通块同样用 \(dsu\) ,只不过是把 \(A\) \(B\) 并入新节点。
有一个比较明显的性质:最后建成的树一定是一个堆(大根堆小根堆视情况而定),因为边是按大小顺序枚举的,如果我表达不太清楚 \((QAQ)\) ,可以配合图解来学习。
具体来说,如果 \(kruskal\) 求最大生成树的过程是这样的:

那么建树过程大概就是这样的(蓝色是新建节点):

可以很直观地看到这真的是个堆,上文已经提到,不再赘述。
\(u,v\) 之间的瓶颈路,即:\(u,v\) 在重构树中的 \(lca\) 的点权。因为建树过程和 \(kruskal\) 同步进行,所以每个新建节点的点权是可以连接两棵子树所在联通块的最大边权,同时因为堆的性质 \(lca\) 是整颗以 \(lca\) 为根的子树中的最小值。(本人语文水平不及格,可以结合图片和 \(kruskal\) 的过程来理解)
\(p.s.\) 同样可以利用该性质证明瓶颈路一定在最大生成树上。
例题
\((noip2013\) \(d1t3)\)
这是一道模板题。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
inline int in() {
int x=0;char c=getchar();bool f=false;
while(c<'0'||c>'9') f|=c=='-', c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48), c=getchar();
return f?-x:x;
}
const int N = 1e5+5, M = 5e5+5;
struct info {
int l, r, w;
inline bool operator < (const info &x) const {
return this->w > x.w;
}
}a[M+N];
int n, m, tot, fa[N<<1];
namespace kruskal_tree {
struct edge {
int next, to;
}e[N];
int head[N<<1], val[N<<1], cnt=1, size[N<<1], hson[N<<1], fro[N<<1], dep[N<<1];
inline void jb(int u, int v) {
e[++cnt]=(edge){head[u], v};
head[u]=cnt;
}
void dfs_h(int u) {
size[u]=1;
for(int i=head[u];i;i=e[i].next) {
int v=e[i].to;
fa[v]=u, size[u]+=size[v], dep[v]=dep[u]+1;
if(size[v]>size[hson[u]]) hson[u]=v;
dfs_h(v);
}
}
void dfs_f(int u, int father) {
fro[u]=father;
if(hson[u]) dfs_f(hson[u], father);
for(int i=head[u];i;i=e[i].next)
if(e[i].to!=hson[u]) dfs_f(e[i].to, e[i].to);
}
inline int lca(int u, int v) {
while(fro[u]!=fro[v]) {
if(dep[fro[u]]>dep[fro[v]]) std::swap(u, v);
v=fa[fro[v]];
}
return dep[u]<dep[v]?u:v;
}
inline void pre() {
dep[tot]=1;
dfs_h(tot); dfs_f(tot, tot);
}
inline int calc(int u, int v) {
return val[lca(u, v)];
}
}
int get_fa(int u) {
return fa[u]==u?u:fa[u]=get_fa(fa[u]);
}
inline void kruskal(info *e) {
tot=n;
std::sort(e+1, e+1+m);
for(int i=1;i<=n<<1;++i) fa[i]=i;
for(int i=1;i<=m;++i) {
int fx=get_fa(e[i].l), fy=get_fa(e[i].r);
if(fx==fy) continue;
fa[fx]=fa[fy]=++tot, kruskal_tree::val[tot]=e[i].w;
kruskal_tree::jb(tot, fx), kruskal_tree::jb(tot, fy);
}
kruskal_tree::pre();
}
int main() {
n=in(), m=in();
for(int i=1;i<=m;++i)
a[i].l=in(), a[i].r=in(), a[i].w=in();
for(int i=1;i<=n;++i)
a[++m]=(info){0, i, -1};
kruskal(a);
int q=in();
while(q--) {
int u=in(), v=in();
printf("%d\n", kruskal_tree::calc(u, v));
}
return 0;
}
\((noi2018\) \(d1t1)\)
一句话题解: \(dijsktra\) 预处理 \(1\) 为起点的最短路(权值为长度);建好 \(kruskal\) 重构树(海拔为权值),每次查询 \(v\) 时树上倍增一直跳就好了 \(qwq\)。
时间复杂度:\(\Theta \Big(T \times \big (mlogm+(q+n)logn \big) \Big)\)
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
typedef long long ll;
inline int in() {
int x=0;char c=getchar();bool f=false;
while(c<'0'||c>'9') f|=c=='-', c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48), c=getchar();
return f?-x:x;
}
const int N = 2e5+5, M = 4e5+5;
struct info {
int l, r, w;
friend inline bool operator < (const info &x, const info &y) {
return x.w > y.w;
}
}a[M];
struct edge {
int next, to, w;
}e[M<<1];
int head[N], cnt, n, m, tot, fa[M], d[N];
bool vis[N];
template<typename T>
inline void chk_min(T &_, T __) { _=_<__?_:__; }
namespace kt {
struct edge {
int next, to;
}e[N<<1];
int cnt=1, head[N<<1], min[N<<1], dep[N<<1], fa[19][N<<1], a[N<<1], L[19];
inline void jb(int u, int v) {
e[++cnt]=(edge){head[u], v};
head[u]=cnt;
}
void dfs(int u) {
for(int i=1;i<=18;++i)
if(dep[u]>L[i]) fa[i][u]=fa[i-1][fa[i-1][u]];
else break;
min[u]=2147483647;
if(1<=u&&u<=n) min[u]=d[u], a[u]=2147483647;
for(int i=head[u];i;i=e[i].next) {
int v=e[i].to;
dep[v]=dep[u]+1, fa[0][v]=u;
dfs(v);
chk_min(min[u], min[v]);
}
}
inline int query(int u, int s) {
for(int i=18;i>=0;--i)
if(dep[u]>L[i]&&a[fa[i][u]]>s)
u=fa[i][u];
return min[u];
}
}
inline void add_edge(int u, int v, int w) {
e[++cnt]=(edge){head[u], v, w}, head[u]=cnt;
e[++cnt]=(edge){head[v], u, w}, head[v]=cnt;
}
typedef std::pair <int, int> pii;
inline void dijkstra() {
std::priority_queue <pii> q;
memset(d, -1, sizeof(d));
memset(vis, false, sizeof(vis));
d[1]=0;
q.push(pii(0, 1));
while(!q.empty()) {
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];i;i=e[i].next) {
int v=e[i].to;
if(d[u]+e[i].w<d[v]||d[v]==-1)
d[v]=d[u]+e[i].w, q.push(pii(-d[v], v));
}
}
}
int get_fa(int u) {
return fa[u]==u?u:fa[u]=get_fa(fa[u]);
}
inline void kruskal(info *e) {
tot=n;
for(int i=1;i<=n<<1;++i) fa[i]=i;
std::sort(e+1, e+1+m);
for(int i=1;i<=m;++i) {
int fx=get_fa(e[i].l), fy=get_fa(e[i].r);
if(fx==fy) continue;
++tot, kt::a[tot]=e[i].w;
kt::jb(tot, fx), kt::jb(tot, fy);
fa[fx]=fa[fy]=tot;
}
}
inline void init() {
cnt=kt::cnt=1;
memset(head, 0, sizeof(head));
memset(kt::head, 0, sizeof(kt::head));
}
int main() {
int T=in();
kt::L[0]=1;
for(int i=1;i<=18;++i) kt::L[i]=kt::L[i-1]<<1;
while(T--) {
init();
n=in(), m=in();
for(int i=1, u, v, l, h;i<=m;++i) {
u=in(), v=in(), l=in(), h=in();
a[i]=(info){u, v, h};
add_edge(u, v, l);
}
dijkstra();
kruskal(a);
kt::dep[tot]=1;
kt::dfs(tot);
int q=in(), k=in(), s=in(), ans=0, v, a;
while(q--) {
int v=(in()+(ll)k*ans-1)%n+1, a=(in()+(ll)k*ans)%(s+1);
ans=kt::query(v, a);
printf("%d\n", ans);
}
}
return 0;
}
kruskal重构树学习笔记的更多相关文章
- Kruskal重构树学习笔记+BZOJ3732 Network
今天学了Kruskal重构树,似乎很有意思的样子~ 先看题面: BZOJ 题目大意:$n$ 个点 $m$ 条无向边的图,$k$ 个询问,每次询问从 $u$ 到 $v$ 的所有路径中,最长的边的最小值. ...
- P4197 Peaks [克鲁斯卡尔重构树 + 主席树][克鲁斯卡尔重构树学习笔记]
Problem 在\(Bytemountains\)有\(n\)座山峰,每座山峰有他的高度\(h_i\) .有些山峰之间有双向道路相连,共\(M\)条路径,每条路径有一个困难值,这个值越大表示越难走, ...
- 洛谷P4197 Peaks&&克鲁斯卡尔重构树学习笔记(克鲁斯卡尔重构树+主席树)
传送门 据说离线做法是主席树上树+启发式合并(然而我并不会) 据说bzoj上有强制在线版本只能用克鲁斯卡尔重构树,那就好好讲一下好了 这里先感谢LadyLex大佬的博客->这里 克鲁斯卡尔重构树 ...
- [算法模板]Kruskal重构树
[算法模板]Kruskal重构树 kruskal重构树是一个很常用的图论算法.主要用于解决u->v所有路径上最长边的最小值,就是找到\(u->v\)的一条路径,使路径上的最长边最小. 图片 ...
- 【学习笔记】Kruskal 重构树
1. 例题引入:BZOJ3551 用一道例题引入:BZOJ3551 题目大意:有 \(N\) 座山峰,每座山峰有他的高度 \(h_i\).有些山峰之间有双向道路相连,共 \(M\) 条路径,每条路径有 ...
- [学习笔记]kruskal重构树 && 并查集重构树
Kruskal 重构树 [您有新的未分配科技点][BZOJ3545&BZOJ3551]克鲁斯卡尔重构树 kruskal是一个性质优秀的算法 加入的边是越来越劣的 科学家们借这个特点尝试搞一点事 ...
- 算法学习——kruskal重构树
kruskal重构树是一个比较冷门的数据结构. 其实可以看做一种最小生成树的表现形式. 在普通的kruskal中,如果一条边连接了在2个不同集合中的点的话,我们将合并这2个点所在集合. 而在krusk ...
- [luogu P4197] Peaks 解题报告(在线:kruskal重构树+主席树 离线:主席树+线段树合并)
题目链接: https://www.luogu.org/problemnew/show/P4197 题目: 在Bytemountains有N座山峰,每座山峰有他的高度$h_i$.有些山峰之间有双向道路 ...
- Kruskal重构树——[NOI2018] 归程
题目链接: UOJ LOJ 感觉 Kruskal 重构树比较简单,就不单独开学习笔记了. Statement 给定一个 \(n\) 点 \(m\) 边的无向连通图,用 \(l,a\) 描述一条边的长度 ...
随机推荐
- 三分钟明白 Activiti工作流 -- java运用
原文地址:https://blog.csdn.net/jiangyu1013/article/details/73250902 一. 什么是工作流 以请假为例,现在大多数公司的请假流程是这样的 员工打 ...
- day08(字符编码,字符与字节,文件操作)
一,复习 ''' 类型转换 1.数字类型:int() | bool() | float() 2.str与int: int('10') | int('-10') | int('0') | float(' ...
- 转:SVN 版本服务器搭配全过程详解(含服务端、客户端)
1.为什么要用VisualSVN Server,而不用Subversion? 回答: 因为如果直接使用Subversion,那么在Windows 系统上,要想让它随系统启动,就要封装SVN Serve ...
- babel 插件编写
一.开始 工具链接: 每一个节点都有如下所示的接口(Interface): interface Node { type: string; } 字符串形式的 type 字段表示节点的类型(如: &quo ...
- NodeJs连接操作MongoDB数据库
NodeJs连接操作MongoDB数据库 一,介绍 MongoDB是一种文档导向数据库管理系统,由C++撰写而成.介绍如何使用 Node.js 来连接 MongoDB,并对数据库进行操作. Mongo ...
- P3373 【模板】线段树 2
线段树的模板,但是还应注意维护乘标记,乘法的优先级大于加法,一定记得还要取模. #include<bits/stdc++.h> using namespace std; ; struct ...
- Python进阶7--正则表达式
正则表达式*** 概述 分类 基本语法 元字符 ^ 匹配字符串的开头 $ 匹配字符串的末尾. . 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符. [.. ...
- java订单金额分级计算
package ord; import java.util.ArrayList; public class order { public String orderid; public user use ...
- jsonp 实现前端跨域
1.基于ajax 发起jsonp 请求. 前端代码: let url = 'http://localhost:8001/'; $.ajax({ type: 'get', dataType: 'json ...
- 搭建一个简单的本地的dubbo-demo案例
一.创建一个Maven工程,然后创建三个module模块 二.dubbo-api(maven模块) 创建一个api类,命名为ApiService.java package com.example.se ...