Kruskal 重构树

Kruskal 重构树#

如何构造#

先把边从小到大排序

Kruskal 合并两个点 ab 所在的集合时,新建一个节点 x 同时连接 ab,且 x 的点权等于 ab 之间的边权

如这样一个图

我们先把边权从小到大排序

合并 13 时,新建点权为 1 的节点 5 ,连接 1535

合并 23 时,新建点权为 2 的节点 6 ,连接 2636

合并 34 时,新建点权为 4 的节点 7 ,连接 3747

最后构造出的 Kruskal 重构树 如下:

若有 n 个点,这样构造一棵 Kruskal 重构树的时间复杂度是 O(nlogn)

性质#

  • 树上除叶子结点以外的点都对应着原来图中的边,叶子结点就是原来图上的节点

  • 从每个点到根节点上除叶子结点外按顺序访问到的点的点权是单调的

  • 出于 Kruskal 算法贪心的性质,两个点 uvlca 的点权就对应着它们最小生成树上的最大值,也是原图中两个点之间的所有简单路径上最大边权的最小值

  • 这棵树是一个二叉堆

习题#

P4768 归程#

剩下四组数据

题意

n 个点,m 条边,保证图联通,每条边有两个权值,一个长度 l,一个海拔 y

多组询问,告诉你起点和水位线,小于等于水位线的边都会被淹没,只能走路,否则可以开车

问从当天起点到1号节点最少步行经过的长度,有些询问会强制在线

思路

x1 最小步行的长度 =min{dis(1,y)},前提是 xy 的路上海拔全大于 S

那么可以先跑一边从 1 到其他所有点的最短路

注意这里要用 dijkstra 因为 SPFA 死了

然后问题就转变为求 存在一条到 x 的路径海拔均大于 S 的点 y 组成的集合中 dis(1,y) 的最小值

很显然可以用 Kruskal 重构树了

我们先按照边的海拔值从大到小排序,然后跑一遍 Kruskal 重构树

这样对于树内的每一个节点 x ,他的所有祖先的海拔值自下而上递减

并且对于每一个节点 x 记录 d(x) 表示其子树内到 1 距离的最小值

搞一下倍增,找到 x 最上的祖先 y 使得 a(y)>Sa(fa(y))s

这样 y 的子树内都是可以通过 x 开车到达的点

d(y) 即为答案

这样时间复杂度是 O(T×(n+m+q)logn)

#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

#define pb push_back
#define mp make_pair
#define pii pair <int,int>
#define int long long

const int N=6e5+5;
const int M=1e6+5;
const int inf=1e18;

struct edge{
	int x,y,l,a,id;
	bool operator < (const edge X) const{
		return a>X.a;
	}
}e[M];

struct node{
	int x,d;
	bool operator < (const node X) const{
		return d>X.d;
	}
};

int n,m,rt;
int q,k,s,lastans;
int f[N][20],g[N],son[N][2];
int dep[N],v[N],d[N];
bool vis[N];
vector <pii> G[N];

inline void dijkstra(){
	priority_queue <node> q;
	for(int i=2;i<=2*n;++i) d[i]=inf,vis[i]=0;
	d[1]=0;
	q.push({1,0});
	while(!q.empty()){
		int x=q.top().x;
		q.pop();
		vis[x]=1;
		for(auto y:G[x]){
			int to=y.first,val=y.second;
			if(vis[to]) continue;
			if(d[to]>d[x]+val){
				d[to]=d[x]+val;
				q.push({to,d[to]});
			}
		}
	}
}

inline int get(int x){
	return x==g[x]?x:g[x]=get(g[x]);
}

inline void kruskal(){
	int num=0;
	sort(e+1,e+m+1);
	for(int i=1;i<=2*n;++i) g[i]=i;
	for(int i=1;i<=m;++i){
		int x=e[i].x,y=e[i].y,l=e[i].l,a=e[i].a,id=e[i].id;
		int fx=get(x),fy=get(y);
		if(fx==fy) continue;
		g[fy]=g[fx]=f[fx][0]=f[fy][0]=++rt;
		son[rt][0]=fx,son[rt][1]=fy;
		f[rt][0]=0;
		v[rt]=a;
		++num;
		if(num==n-1) break;
	}
}

inline int dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	for(int i=1;i<=19;++i) f[x][i]=f[f[x][i-1]][i-1];
	if(son[x][0]) d[x]=min(dfs(son[x][0],x),d[x]);
	if(son[x][1]) d[x]=min(dfs(son[x][1],x),d[x]);
	return d[x];
}

inline int query(int x,int y){
	for(int i=19;i>=0;--i)
		if(dep[x]>(1<<i)&&v[f[x][i]]>y) x=f[x][i];
	return d[x];
}

inline void solve(){
	memset(f,0,sizeof(f));
	memset(son,0,sizeof(son));
	n=read(),m=read();rt=n;
	for(int i=1;i<=n;++i) G[i].clear();
	for(int i=1;i<=m;++i){
		int x=read(),y=read(),l=read(),a=read();
		e[i]=(edge){x,y,l,a,i};
		G[x].pb(mp(y,l));
		G[y].pb(mp(x,l));
	}
	dijkstra();
	kruskal();
	dfs(rt,0);
	q=read(),k=read(),s=read();
	lastans=0;
	while(q--){
		int x=read(),y=read();
		x=(k*lastans+x-1)%n+1,y=(k*lastans+y)%(s+1);
		lastans=query(x,y);
		printf("%lld\n",lastans);
	}
}

signed main(){
	int T=read();
	while(T--)
		solve();
}

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/16451336.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
历史上的今天:
2020-07-06 浅谈树状数组
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示