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\) 描述一条边的长度 ...
随机推荐
- vue动态设置初始页
- java+testng接口测试入门
testNG是一个测试框架,它能组织测试用例按照你想要的方式进行运行,并输出一定格式的便于阅读的测试报告(结果),通过java+testng的方式说明一下接口测试的基本使用方法. 一.环境搭建 a)千 ...
- phpcms不能批量更新栏目页和内容页
需要给网站根目录更加users用户的写入权限.
- C#创建安装、卸载部署程序
分享3: 需求:对已经开发的应用程序进行安装封装操作,即创建安装.卸载部署程序: 分析:程序的开发是为了在不同的人在不同的机器上使用,为了使不同机器使用该软件就需要见程序安装包,并且保证安装包中必须包 ...
- Zookeeper+Kafka集群部署(转)
Zookeeper+Kafka集群部署 主机规划: 10.200.3.85 Kafka+ZooKeeper 10.200.3.86 Kafka+ZooKeeper 10.200.3.87 Kaf ...
- React Native & ES6 & emoji
React Native & ES6 & emoji && 逻辑运算符 https://developer.mozilla.org/zh-CN/docs/Web/Jav ...
- 使用excel整理脚本
的时候需要通过excel数据初始化脚本,当数据过多的时候,脚本也就很多.这里记录一个平时用excel初始化脚本的小技巧. excel中在空单元格中写如下值: ="INSERT INTO db ...
- Leetcode 4.28 string
1. 38. Count and Say 就是对于前一个数,找出相同元素的个数,把个数和该元素存到新的string里.数量+字符 class Solution { public String coun ...
- python 学习二
什么是JSON,JSON 是一种轻量级的数据格式,其实就是字符串 把字符串转换为字典用 json.loads() import jsons = '{"aa":1,"bb& ...
- 用Python将一个列表分割成小列表
用Python将一个列表分割成小列表 2018年01月15日 11:09:25 幸福丶如此 阅读数:16842 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...