Kruskal重构树小记

模拟赛考了,简单贺一下 oi-wiki

引入

定义

在跑 Kruskal 的过程中我们会从小到大加入若干条边。现在我们仍然按照这个顺序。

首先新建 n 个集合,每个集合恰有一个节点,点权为 0

每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。

不难发现,在进行 n1 轮之后我们得到了一棵恰有 n 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫 Kruskal 重构树。

性质

  • 原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值。

  • 到点 x 的简单路径上最大边权的最小值 val 的所有点 y 均在 Kruskal 重构树上的某一棵子树内,且恰好为该子树的所有叶子节点。该子树的根节点就是 x 到根的路径上权值 val 的最浅的节点

一些习题

P4768 [NOI2018] 归程

我们首先需要知道每个点 v 在给定水位线 p 的情况下能够开车到达哪些点。这就需要 kruskal 重构树出场了。将海拔从大到小加入,建立重构树,倍增找到 v 到根节点路径上权值 >p 的最浅的节点,以该节点为根的子树内部均可以用车互相到达。

考虑要在哪里下车,显然就是子树内与 1 距离最近的点。可以先跑 dijkstra 预处理所有点与 1 的最短路,然后在 dfs 处理倍增数组时顺便维护子树最小值即可

code
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;

const int N=200010,M=400010;

int T,n,m,q,k,mx;
struct node{int x,y,z,h;}e[M];
bool v[N];

namespace GR
{
	const int MM=2*M;
	int head[N],ver[MM],nxt[MM],edge[MM],tot;
	int d[N];  bool v[N];
	
	void init()
	{
		tot=0;
		memset(head,0,sizeof(head));
	}
	
	void add(int x,int y,int z)
	{
		ver[++tot]=y;  edge[tot]=z;
		nxt[tot]=head[x];  head[x]=tot;
	}
	
	void dijkstra(int s)
	{
		priority_queue <pii> q;
		memset(v,0,sizeof(v));
		memset(d,0x3f,sizeof(d));
		
		d[s]=0;  q.push({0,s});
		while(q.size())
		{
			int x=q.top().second;  q.pop();
			if(v[x])
				continue;
			v[x]=1;
			
			for(int i=head[x]; i; i=nxt[i])
			{
				int y=ver[i],z=edge[i];
				if(d[x]+z<d[y])
					d[y]=d[x]+z,q.push({-d[y],y});
			}
		}
	}
}

namespace TR
{
	const int NN=2*N;
	int fa[NN],cnt,dep[NN],f[NN][25],val[NN],sub[NN];
	vector <int> g[NN];
	
	void init()
	{
		memset(dep,0,sizeof(dep));
		memset(f,0,sizeof(f));
		memset(val,0,sizeof(val));
		memset(sub,0x3f,sizeof(sub));
		for(int i=1; i<=2*n; i++)
			g[i].clear();
	}
	
	void add(int x,int y)
	{
		g[x].push_back(y);
		g[y].push_back(x);
	}
	
	bool cmp(node a,node b)
	{
		return a.h>b.h;
	}
	
	int get(int x)
	{
		if(x==fa[x])
			return x;
		return fa[x]=get(fa[x]);
	}
	
	void kruskal()
	{
		sort(e+1,e+1+m,cmp);
		for(int i=1; i<=2*n; i++)
			fa[i]=i;	
			
		cnt=n;  int t=0;
		for(int i=1; i<=m; i++)
		{
			int fx=get(e[i].x),fy=get(e[i].y);
			if(fx==fy)
				continue;
			fa[fx]=fa[fy]=++cnt;
			add(cnt,fx);  add(cnt,fy);
			val[cnt]=e[i].h;
			t++;
			if(t==n-1)
				break;
		} 
	}
	
	void dfs(int x,int fa)
	{
		if(x<=n)
			sub[x]=GR::d[x];
		for(auto y:g[x])
		{
			if(y==fa)
				continue;
				
			dep[y]=dep[x]+1;
			f[y][0]=x;
			for(int i=1; i<=20; i++)
				f[y][i]=f[f[y][i-1]][i-1];
			dfs(y,x);
			sub[x]=min(sub[x],sub[y]);
		}
	}
	
	int find(int x,int p)
	{
		for(int i=20; i>=0; i--)
			if(val[f[x][i]]>p)
				x=f[x][i];
		return x;
	}
}

int main()
{
	using namespace GR;
	using namespace TR;
	
	scanf("%d",&T);
	while(T--)
	{
		GR::init();  TR::init();
		
		scanf("%d%d",&n,&m);
		for(int i=1; i<=m; i++)
		{
			scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].z,&e[i].h);
			GR::add(e[i].x,e[i].y,e[i].z);  GR::add(e[i].y,e[i].x,e[i].z);
		}
		
		dijkstra(1);
		
		kruskal();
		dfs(2*n-1,0);
		
		int last=0;
		scanf("%d%d%d",&q,&k,&mx);
		for(int i=1; i<=q; i++)
		{
			int v,p;
			scanf("%d%d",&v,&p);
			v=(v+k*last-1)%n+1;
			p=(p+k*last)%(mx+1);
			
			int tur=find(v,p); 
			last=sub[tur];
			
			printf("%d\n",last);
		}
	}
	
	return 0;
}

P7834 [ONTAK2010] Peaks 加强版

重构树上跑主席树。注意到重构树上叶子节点才有贡献,所有叶子节点构成一个区间,所以可以维护一个非叶子节点所覆盖的区间,方便操作

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=100010,M=500010;

int n,m,q,a[N],rk[N],b[N];
struct node{int x,y,z;}e[M];

namespace TR
{
	int cnt,fa[2*N],val[2*N],f[2*N][20];
	int L[2*N],R[2*N],id[N],top,rt[2*N];
	bool vis[2*N];
	vector <int> g[2*N];
	
	void add(int x,int y)
	{
		g[x].push_back(y);
	}

	bool cmp(node a,node b)
	{
		return a.z<b.z;
	}

	int get(int x)
	{
		if(x==fa[x])
			return x;
		return fa[x]=get(fa[x]);
	}

	void kruskal()
	{
		for(int i=1; i<=2*n; i++)
			fa[i]=i;

		cnt=n;  val[0]=1e16;
		for(int i=1; i<=m; i++)
		{
			int fx=get(e[i].x),fy=get(e[i].y);
			if(fx==fy)
				continue;
			fa[fx]=fa[fy]=++cnt;
			add(cnt,fx);  add(cnt,fy);
			val[cnt]=e[i].z;
		}
	}

	void dfs(int x,int fa)
	{
		L[x]=top;  vis[x]=1;
		if(g[x].size()==0)
			id[++top]=x;
			
		for(auto y:g[x])
		{
			if(y==fa)
				continue;

			f[y][0]=x;
			for(int i=1; i<=18; i++)
				f[y][i]=f[f[y][i-1]][i-1];
			dfs(y,x);
		}
		R[x]=top;
	}

	int find(int x,int p)
	{
		for(int i=18; i>=0; i--)
			if(val[f[x][i]]<=p)
				x=f[x][i];
		return x;
	}
}

namespace SegmentTree
{
	int tot=0;
	struct Seg
	{
		int lc,rc,sum;
		#define lc(x) tree[x].lc
		#define rc(x) tree[x].rc
		#define sum(x) tree[x].sum
	}tree[40*N];

	void pushup(int p)
	{
		sum(p)=sum(lc(p))+sum(rc(p));
	}
	
	void build(int &p,int l,int r)
	{
		p=++tot;
		if(l==r)
		{
			sum(p)=0;
			return;
		}

		int mid=(l+r)>>1;
		build(lc(p),l,mid);
		build(rc(p),mid+1,r);
		pushup(p);
	}

	void change(int &p,int pre,int l,int r,int x,int v)
	{
		p=++tot;
		tree[p]=tree[pre];
		if(l==r)
		{
			sum(p)+=v;
			return;
		}
		
		int mid=(l+r)>>1;
		if(x<=mid)
			change(lc(p),lc(pre),l,mid,x,v);
		else
			change(rc(p),rc(pre),mid+1,r,x,v);
		pushup(p);
	}
	
	int ask(int p,int pre,int l,int r,int k)
	{
		if(sum(p)-sum(pre)<k)
			return -1;
		if(l==r)
			return l;
		
		int mid=(l+r)>>1;
		int rcnt=sum(rc(p))-sum(rc(pre)); 
		if(rcnt>=k)
			return ask(rc(p),rc(pre),mid+1,r,k);
		return ask(lc(p),lc(pre),l,mid,k-rcnt);
	}
}

signed main()
{
	using namespace TR;
	using namespace SegmentTree;
	
	scanf("%lld%lld%lld",&n,&m,&q);
	for(int i=1; i<=n; i++)
		scanf("%lld",&a[i]),rk[i]=a[i];
	for(int i=1; i<=m; i++)
		scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].z);

	sort(rk+1,rk+1+n);
	int nn=unique(rk+1,rk+1+n)-(rk+1);
	for(int i=1; i<=n; i++)
	{
		int x=lower_bound(rk+1,rk+1+nn,a[i])-rk;
		b[x]=a[i];  a[i]=x;
	}
	
	sort(e+1,e+1+m,cmp);

	kruskal();

	for(int i=1; i<=cnt; i++)
		if(!vis[i])
			dfs(get(i),0);

	build(rt[0],1,n);
	for(int i=1; i<=n; i++)
		change(rt[i],rt[i-1],1,n,a[id[i]],1);

	int last=0;
	while(q--)
	{
		int v,x,k;
		scanf("%lld%lld%lld",&v,&x,&k);
		v=(v^last)%n+1;  k=(k^last)%n+1;  x^=last;
		int tur=find(v,x);  
		int res=ask(rt[R[tur]],rt[L[tur]],1,n,k);
		
		if(res==-1)
			puts("-1"),last=0;
		else
			printf("%lld\n",last=b[res]);
	}

	return 0;
}

CF1706E Qpwoeirut and Vertices

把编号作为边权,即求 [l,r] 任意两点的 LCA 最大值。用线段树维护区间 id 最小值和最大值即可

CF1416D Graph and Queries

可以反向考虑加边,正向考虑亦可。离线询问,把边的删除时间作为边权建立小根堆,查询时树上倍增找到 val> 当前询问时间的深度最浅的点,求子树最大值并删除。拿线段树维护

posted @   xishanmeigao  阅读(4)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示