kruskal重构树

kruskal重构树

本质就是把边权改变成了点权,如果原图的结点数为 (n),那么一般情况下kruskal重构树的结点数为 (2n-1)

构造过程

首先我们回忆kruskal算法求解最小生成树的过程

  • 模拟最小生成树kruskal算法
  • 如果我们找到了两个不在同一集合的点 (x,y),就新建一个结点 (n) 并向 (x,y) 的祖先结点分别连边
  • 将点 (n) 的点权设为连接 (x,y) 的边的边权

性质

很容易发现,经过这样的构造过程构造的东西是一个二叉树,并且满足以下性质

  • 这个二叉树也是一个二叉堆
  • 原最小生成树的两点间的最大边权就是重构树上两点的LCA的点权
  • 重构树中有且仅有叶节点代表了原树上的节点,其余的点全部代表边权

利用这些性质,我们可以方便的解决很多问题

或者说kruskal重构树像CDQ分治一样就是一种思想?

例题

P1967 [NOIP2013 提高组] 货车运输

题目链接

题面描述

给定一张一般图,对于所有连接两点 (x,y) 的路径,找出一个最小边权最大的路径并输出

题目分析

超级简单基本题

对于任意两点,最小边权最大的路径一定在这张图的最大生成树上

直接建出kruskal重构树即可

根据其性质,这条路径的最小边权一定在重构树的LCA上,输出其点权即可

代码实现

  • 初始化并查集
  • 模拟kruskal算法构造重构树
  • 树链剖分求LCA,输出点权

注意到本题的图并不一定连通,我们需要类似tarjan算法的循环来保证每个连通块都能被正确求解

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define R register
#define C const
using namespace std;
int n,m,fa[N],x,y,node,q;
struct Edge{
	int f,t,w;
	fx(bool,operator) < (const Edge &b) const{
		return w>b.w;
	}
}e[N];
struct kruskal{
	int ls,rs,val,hs,dep,siz,top,fa;
}k[N];
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(int,find)(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
fx(void,dfs1)(int now,int dep){
	k[now].dep=dep;k[now].siz=1;
	if(k[now].ls){
		dfs1(k[now].ls,dep+1);
		k[now].siz+=k[k[now].ls].siz;
		if(k[k[now].ls].siz>k[k[now].hs].siz) k[now].hs=k[now].ls;
	}
	if(k[now].rs){
		dfs1(k[now].rs,dep+1);
		k[now].siz+=k[k[now].rs].siz;
		if(k[k[now].rs].siz>k[k[now].hs].siz) k[now].hs=k[now].rs;
	}
}
fx(void,dfs2)(int now,int top){
	k[now].top=top;
	if(!k[now].hs) return;
	dfs2(k[now].hs,top);
	int nx;
	if(k[now].hs==k[now].ls) nx=k[now].rs;
	else nx=k[now].ls;
	dfs2(nx,nx);
}
fx(int,LCA)(int x,int y){
	while(k[x].top^k[y].top){
		if(k[k[x].top].dep>k[k[y].top].dep) x=k[k[x].top].fa;
		else y=k[k[y].top].fa;
	}
	return k[x].dep>k[y].dep?y:x;
}
signed main(){
	n=gi(),m=gi();node=n;
	for(int i=1;i<=((n+m)<<1);i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		e[i].f=gi(),e[i].t=gi(),e[i].w=gi();
	}
	sort(e+1,e+m+1);
	for(int i=1;i<=m;i++){
		x=find(e[i].f);y=find(e[i].t);
		if(x==y) continue;
		else{
			k[++node].ls=x;k[node].rs=y;
			k[x].fa=k[y].fa=node;
			k[node].val=e[i].w;
			fa[x]=fa[y]=node;
		}
	}
	for(int i=1;i<n+m;i++) if(!k[i].fa) dfs1(i,1),dfs2(i,i);
	q=gi();
	for(int i=1;i<=q;i++){
		x=gi(),y=gi();
		if(find(x)^find(y)) printf("-1
");
		else printf("%d
",k[LCA(x,y)].val);
	}
}

P4768 [NOI2018] 归程

题目分析

关于SPFA,它死了

每条边有两个参数:长度和海拔,初看似乎无从下手,因为按海拔来长度不能保证,按长度海拔不能保证

我们如果对车子的作用稍加思考,就会发现每个路径被分成了两段

前面一段是海拔高的,不对答案做贡献的,后面一段是海拔低的,对答案做贡献的

因为终点始终都是 (1),所以后面的一段可以直接Dijkstra预处理

前面一段怎么处理呢?

以起点来看,和起点之间所有路径中满足一条路径上的边上海拔全部高于给定值的点构成了一个连通块,我们要求的就是这个连通块或是与连通块距离不超过 (1) 的所有点到终点的长度的最小值

这显然可以直接上kruskal重构树维护

套个树上倍增,然后你会发现这题已经做完了

简单到爆炸,但是需要注意细节(笔者因为一些细节WA了10多遍,可能这就是弱...)

代码实现

  • 加边的同时跑kruskal重构树
  • Dijkstra最短路预处理
  • 树上倍增
  • Accepted!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 900100
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define R register
#define C const
using namespace std;
int n,m,x,y,head[N],num,dis[N],node,bf,fa[N],par[N][23],T,dn,ty,mh,st,hi,las;
bool vis[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
struct Edge{
	int f,t,w,h;
	fx(bool,operator) < (const Edge &b) const{
		return h>b.h;
	}
}e[N<<2];
struct edge{
	int na,np,len;
}de[N<<2];
struct kruskal{
	int val,high,ls,rs,fa;
}k[N<<2];
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(void,add)(int f,int t,int len){
	de[++num].na=head[f];
	de[num].np=t,de[num].len=len;
	head[f]=num;
}
fx(int,find)(int x){
	if(fa[x]^x) fa[x]=find(fa[x]);
	return fa[x];
}
fx(void,dijkstra)(int s=1){
	set(dis,0x3f,dis,1);set(vis,0,vis,1);
	dis[s]=0;q.push(make_pair(0,s));
	while(!q.empty()){
		bf=q.top().second;q.pop();
		if(vis[bf]) continue;
		vis[bf]=1;
		for(int i=head[bf];i;i=de[i].na){
			if(dis[bf]+de[i].len<dis[de[i].np]){
				dis[de[i].np]=dis[bf]+de[i].len;
				q.push(make_pair(dis[de[i].np],de[i].np));
			}
		}
	}
}
signed main(){
	T=gi();
	while(T--){
		set(par,0,par,1);set(de,0,de,1);set(e,0,e,1);set(head,0,head,1);num=0;las=0;
		set(k,0,k,1);node=0;
		n=gi(),m=gi();
		for(int i=1;i<=(n<<2);i++) fa[i]=i;
		for(int i=1;i<=m;i++){
			e[i].f=gi(),e[i].t=gi();
			e[i].w=gi(),e[i].h=gi();
			add(e[i].f,e[i].t,e[i].w);
			add(e[i].t,e[i].f,e[i].w);
		}
		dijkstra();
		for(int i=1;i<=n;i++){
			node+=1;
			k[node].val=dis[node];
		}
		sort(e+1,e+m+1);
		for(int i=1;i<=m;i++){
			x=find(e[i].f),y=find(e[i].t);
			if(x^y){
				k[++node].ls=x;
				k[node].rs=y;
				par[x][0]=par[y][0]=node;
				k[node].high=e[i].h;
				k[node].val=min(k[x].val,k[y].val);
				fa[x]=fa[y]=node;
			}
		}
		for(int o=1;o<=22;o++)
			for(int i=1;i<=node;i++)
				par[i][o]=par[par[i][o-1]][o-1];
		dn=gi(),ty=gi(),mh=gi();
		for(int i=1;i<=dn;i++){
			st=gi(),hi=gi();
			if(ty){
				st=(st+las-1)%n+1;
				hi=(hi+las)%(mh+1);
			}
			for(int o=22;o>=0;o--)
				if(k[par[st][o]].high>hi&&par[st][o])
					st=par[st][o];
			printf("%d
",las=k[st].val);
		}
	}
}
原文地址:https://www.cnblogs.com/zythonc/p/14563072.html