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

Kruskal重构树 学习笔记

时间:2023-10-12 20:46:32浏览次数:32  
标签:重构 10 int Kruskal 笔记 inline 节点 边权

前言

也许在看这篇文章之前,你可以看看这篇文章

前置知识:\(kruskal\) 求最小生成树,并查集……

算法介绍

问题引入

两个点之间的所有简单路径上最大边权的最小值。

我们定义 \(u\to v\) 路径的瓶颈为,路径上的边权最大值。

那么下图的瓶颈就为 4:
image

同时一条路径也可能有多个瓶颈,求最小瓶颈。

实现方法

在做上面那道例题我们对于边权大的可以不考虑他的贡献,因为我们要的是路径上的最大值越小越好,那么我们可以在原图联通的条件下,尽量去边权小的。

我们想到了在最小生成树的时候学习到了 \(Kruskal\) 算法(就当你看过上面的文章了),我们是按照边权的大小从大到小进行排序,并将两端不联通的两个集合合并在一起。

于是就产生了 \(Kruskal\) 重构树,当我们连接两个点 \(u,v\) 时,我们先找到他们的祖先 \(fau,fav\),考虑创建一个新节点 \(nod\),使得 \(fau,fav\) 的父亲为 \(nod\),关于 \(nod\) 的点权 \(W_{nod}\) 我们通常设 \(W_{nod}\) 设为 \(u\to v\) 这条边的边权(如果是给的是点的限制条件,我们可以设这个点的权值为 \(\max(w_u,w_v)\mid\min(w_u,w_v)\) 这样可以使经过这条边的需要限制条件满足这个边权的值才可以走)。这在我们做题的时候可能会有意想不到的帮助。

对于构建步骤:

  • 新建n个集合,每个集合图中为一个节点,点权为0。

  • 按照边权从小到大顺序遍历边。

  • 判断当前这条边的两个顶点 \(u,v\) 是否在同一集合中,若不是,则新建一个节点 \(nod\),点权为当前边的权值,并且将两个集合的父亲设为 $nod $,然后将两个集合合并,设其根节点为新添加的这个点。

算了,还是模拟一下过程(加粗的为原节点)。

:我没加点权。

我们假设原图长这样:

我们将边从小到大进行排序,得到:

排名 1 2 3 4 5 6 7 8 9
\(u\) 2 2 1 2 3 1 1 4 4
\(v\) 5 3 6 4 4 5 4 6 5
\(w\) 1 2 3 3 4 5 6 7 8

然后开始重构。

将 \((2,5)\) 这条边加入,先找到 \(2,5\) 的祖先 \(2,5\),新建节点 \(7\),将 \(2\to 7,5\to 7\),现在的图应该是:

然后再加入 \((2,3)\) 这条边,先找到 \(2,3\) 的祖先 \(7,3\),新建节点 \(8\),将 \(7\to 8,3\to 8\)。

再加入 \((1,6)\) 这条边,找到 \(1,6\) 的祖先 \(1,6\),新建节点 \(9\),将 \(1\to 9,6\to 9\)。

再加入 \((2,4)\) 这条边,找到 \(2,4\) 的祖先 \(8,4\),新建节点 \(10\),将 \(4\to 10,8\to 10\)。

加入 \((1,5)\) 这条边,找到 \(2,4\) 的祖先 \(10,10\),发现他们是一个子树内的,不需要加进去。

加入 \((1,5)\) 这条边,找到 \(1,5\) 的祖先 \(9,10\),新建节点 \(11\),将 \(9\to 11,10\to 11\)。

$Kruskal$ 重构树模版
struct XXX{
    int u,v,w;
}a[600005];
inline bool cmp(XXX x,XXX y) {
	return x.w<y.w;
}

int find(int fa){
	if(f[fa]==fa) return fa;
	return f[fa]=find(f[fa]);
}
int merge(int u,int v){
	int fau=find(u),fav=find(v);
	f[fav]=fau;
}
int tot;
vector<int>q[600007];
inline void addEdge(int u, int v){
    q[u].push_back(v);
    q[v].push_back(u);
}
int W[600007];
inline void Kruskal() {
	for(int i=1;i<=2*n+m;i++) f[i]=i;
	sort(a+1,a+m+1,cmp);
	for (register int i=1;i<=m;i++){
		XXX e=a[i];
		int u=e.u,v=e.v,w=e.w;
		if(find(u)!=find(v)){
			int fau=find(u),fav=find(v);
			int nod=++tot;
			W[nod]=w;
			f[nod]=nod;
			addEdge(nod,fau);
			addEdge(nod,fav);
            merge(tot,fau);
			merge(tot, fav);
		}
        if (tot==n*2-1)
            break;
	}
}

性质

  • 重构树是一个二叉树,且如果原图有 \(n\) 个点,那么重构树有 \(2*n-1\) 个点,树上的每一个叶节点都是原图上的点。

  • 原生成树中两个节点之间简单路径的最大边权的最小值即为重构树中这两个点的最近公共祖先。

  • 在对于只经过全值不大于某个点或边的权值,我们只需要找到他祖先中,权值小于等于限制条件且深度最浅的一个点,它的所有子节点及时可以走到的所有地方。

例题

原题:P4768 [NOI2018] 归程

我们发现 \(v\) 到 \(u\) 的路径一定在 \(u\) 到 \(v\) 的最大生成树上。

考虑把边按照海拔进行从大到小排序,并求出该图的 \(Kruskal\) 重构树,然后对于 \(x\) 的祖先,只要有 \(W_x\le p\) 则我们一定可以走到 \(x\)(\(Kruskal\) 重构树的性质)。

我们乘车的目的就是选择一个到一号点最短路最小的点下车,那么可以先跑一遍 \(dijkstra\) 算出一号节点与其他点的最短路径。

然后我们可以考虑倍增(一般的图论都需要倍增),利用倍增直接找到与 \(1\) 距离最小且 \(W_x\le p\) 的祖先即可。

P4768 [NOI2018] 归程
#include<bits/stdc++.h>
using namespace std;
inline int read() {
	int x=0,f=1;
	char c=getchar();
	while(!isdigit(c)) {
		if(c=='-')
			f=-1;
		c=getchar();
	}
	while(isdigit(c))
		x=x*10+c-'0',c=getchar();
	return x*f;
}
int n,m;
int Tot;
struct Edge {
    int to, v, w;
}grape[800005];
vector<Edge>G[800005];
inline void AddEdge(int u, int v, int z, int w)
{
    G[u].push_back((Edge){v,z,w });
    G[v].push_back((Edge){ u,z,w });
}
inline bool cmp(Edge x,Edge y) {
	return x.w>y.w;
}
struct DSU {
	int fa[800005];
	inline void dsU(){
		for(register int i=1;i<=n+m;i++)fa[i]=i;
	}
	inline int find(int x){
		if (fa[x]==x)return x;
		return fa[x]=find(fa[x]);
	}
	inline bool same(int u,int v) {
		return find(u)==find(v);
	}
	inline void merge(int u,int v) {
		u=find(u),v=find(v);
		if(u==v) return;
		fa[v]=u;

	}
} dsu;
struct node {
	int wei;
	int id;
	bool operator <(const node& x)const {
		return x.wei<wei;
	}
};
int d[800005];
inline void dij() {
	priority_queue<node> q;
	for(register int i=1;i<=n;i++)
		d[i]=INT_MAX;
	d[1]=0;
	q.push((node){0,1});
	while(!q.empty()){
		node tmp=q.top();
		q.pop();
		int I=tmp.id,W=tmp.wei;
		if(W>d[I])
			continue;
        for(auto& e:G[I]){
            if (d[I]+e.v<d[e.to]){
                d[e.to]=d[I]+e.v;
                q.push((node){d[e.to],e.to});
            }
        }
	}
}
vector<int>Q[800005];
int tot;
int W[800005];
inline void Kruskal() {
	sort(grape+1,grape+m+1,cmp);
	dsu.dsU();
	for (register int i=1;i<=m;i++){
		Edge e=grape[i];
		int u=e.to,v=e.v,w=e.w;
		if (!dsu.same(u, v)){
			int fau=dsu.find(u),fav=dsu.find(v);
			int nod=++tot;
			W[nod]=w;
			dsu.fa[nod]=nod;
			Q[nod].push_back(fau);
			Q[fau].push_back(nod);
			Q[nod].push_back(fav);
			Q[fav].push_back(nod);
            dsu.merge(tot, fau), dsu.merge(tot, fav);
		}
        if (tot==n*2-1)
            break;
	}
}
int f[800005],g[800005][29];
inline void dfs(int x,int p){
    g[x][0]=p;
    if(x<=n) f[x]=d[x];
    else f[x]=INT_MAX;
    for(auto v:Q[x]){
        if(v==p)
            continue;
        dfs(v,x);
        f[x]=min(f[v],f[x]);
    }
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); 
	int T=read();
	while(T--) {
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
		memset(W,0,sizeof(W));
		n=read();
		m=read();
		for(register int i=1;i<=n+m;i++)
			Q[i].clear();
		for(register int i=1;i<=n;i++)
			G[i].clear();
		tot=n;
		for(register int i=1;i<=m;i++){
			grape[i].to=read();
			grape[i].v=read();
			int z=read();
			grape[i].w=read();
			AddEdge(grape[i].to,grape[i].v,z,grape[i].w);
			AddEdge(grape[i].v,grape[i].to,z,grape[i].w);
		}
		dij();
		Kruskal();
		dfs(tot,0);
        for(int i=1;(1<<i)<=tot;i++)
			for(register int u=1;u<=tot;u++)
        		g[u][i]=g[g[u][i-1]][i-1];
		int Ques=read();
		int k=read(),s=read();
		int lastans=0;
		while(Ques--){
			int x=read();
			x=(x+k*lastans-1)%n+1;
			int y=read();
			y=(y+k*lastans)%(s+1);
			for(int i=25;i>=0;--i)
				if(g[x][i]>0&&W[g[x][i]]>y)
					x=g[x][i];
			lastans=f[x];
			cout<<f[x]<<endl;
		}
	}
	return 0;
}

标签:重构,10,int,Kruskal,笔记,inline,节点,边权
From: https://www.cnblogs.com/pdpdzaa/p/KruskalRefactor.html

相关文章

  • 笔记软件快捷键
    Ctrl+shift+【有序列表Ctrl+shift+】无须列表标题:Ctrl+1/2/3/4/5标题大小Ctrl+0段落增大标题级别:Ctrl++减小标题级别:Ctrl+-增加缩进Ctrl+]减少缩进Ctrl+]选中一整行:CTRL+L选中单词:CTRL+D选中相同格式的文字:ctrl+E跳转到文章开头:CTRL+Home跳转到文章结尾:CTRL+e......
  • 动态规划——树形DP 学习笔记
    动态规划——树形DP学习笔记引入前置知识:树基础。树形DP,即在树上进行的DP,最常见的状态表示为\(f_{u,\cdots}\),表示以\(u\)为根的子树的某个东东。本文将讲解一些经典题目(树的子树个数、树的最大独立集、树的最小点覆盖、树的最小支配集、树的直径、树的重心、树的中心),以......
  • 2023/10/12 学习笔记2
    一、信号与数制转换1.1 信号相关概念1.1.1 信息:不同领域对信息有不同的定义,一般认为信息是人们对现实世界事物的存在方式或运动状态的某种认识。表示信息的形式可以是数值、文字、图形、声音、图像及动画等。1.1.2 数据:数据是用于描述事物的某些属性的具体量值。1.1.......
  • docker 部署.net core ,用于博主本人笔记
     安装dockerdocker部署netcore步骤1、下载最新netcore支持dockerpullmcr.microsoft.com/dotnet/core/aspnet:latest2、发布netcore项目linux环境需要在发布文件夹内创建Dockerfile,并添加如下内容--------------------------以下为dockerFile内容--------------------......
  • 二次离线莫队笔记
    前言莫队可以解决许多其他数据结构无法完成的问题,正在很多其他问题上也可以拿部分分甚至满分,只因其复杂度为小常数\(O(n\sqrtn\timesk)\)其中\(k\)是单次扩张以及收缩的复杂度,而二离莫队可以在答案可差分的情况下达到\(O(n\sqrtn+n\timesk)\)起源lxl把二次离线莫......
  • 网络流笔记
    前言粗略地讲一下吧,大概能理解就行理论部分借鉴了oi-wiki,有问题欢迎指出网络流网络是一个特殊有向图$G=(V,E)$,特殊在于有源点$s$和汇点$t$首先网络流图中每条边$(u,v)$都有一个容量$c(u,v)$介绍流函数$f(u,v)$,指$u$到$v$流量所以就会有流量守恒,即$0\leq......
  • 《信息安全系统设计与实现》第六周学习笔记
    一、课程内容第十一章学习EXT2文件数据结构1、通过mkfs创建虚拟磁盘mke2fs[-bblksize-Nninodes]devicenblocks虚拟磁盘布局:2、操作系统内核中的文件系统函数3、系统调用4、I/O库函数5、用户命令6、sh脚本低级别的文件操作中的常用函数:打开和关闭文件:open():打......
  • DR7808 配置笔记
    CSA部分:内部CSA可以配置为单向,或者双向,一共有两个CSA,内部CSA的GAIN可以配置,挡位有10,20,40,80四种增益选项。也可以直接关闭内部CSA,CSA的过流保护值和过流保护滤波时间都可以单独设置。相关寄存器:DR7808_GENCTRL1DR7808_HBIDIAGDR7808_GENCTRL2DR7808_CSA_OC_SH寄存器相关描......
  • C#学习笔记--面向对象三大特征
    C#核心面向对象--封装用程序来抽象现实世界,(万物皆对象)来编程实现功能。三大特性:封装、继承、多态。类与对象声明位置:namespace中样式:class类名{}命名:帕斯卡命名法(首字母大写)实例化对象:根据类来新建一个对象。Personp=newPerson();成员变量声明在类语句块中用来描述......
  • 【刷题笔记】80. Remove Duplicates from Sorted Array II
    题目Givenasortedarraynums,removetheduplicatesin-placesuchthatduplicatesappearedatmosttwiceandreturnthenewlength.Donotallocateextraspaceforanotherarray,youmustdothisbymodifyingtheinputarrayin-placewithO(1)extramemor......