kruskal重构树

直接上一道例题:

给你一张 n n n个点, m m m条边的图, q q q组询问,每次需要求 x , y x,y x,y路径最大边的最小值.

由于我们每次选最小的边加入一定最优,所以原问题可以转化为求一棵 M S T MST MST.

直接讲如何构树:
我们把边权转点权,如果有边 ( x , y , T ) (x,y,T) (x,y,T) M S T MST MST内,我们用一个点连接对应的连通块.

原图:
在这里插入图片描述
重构后:
在这里插入图片描述

具体的,一开始有 n n n个叶子结点,对应着原图的结点.
当有一条边 ( x , y , T ) (x,y,T) (x,y,T)能加入 M S T MST MST时.
我们找到 x , y x,y x,y对应连通块的深度最小的点 u , v u,v u,v.
新建一个结点,连接 u , v u,v u,v并更新并查集.

k r u s k a l kruskal kruskal重构树有以下性质:

  1. 二叉树
  2. 类似根堆
  3. 两点的最大点权为其 l c a lca lca对应的权值.

运用情形:当问题和瓶颈路有关时,如最大边最小,最小边最大的情况下的一些问题求解. 同时,你可以方便地找到合法点集,这个维护一下 d f s dfs dfs序即可.
不足:你不能找出对应的路径.如果一定要找出路径的话,可以考虑树剖或者倍增,如果在线的话考虑 L C T LCT LCT.

那么这道例题就非常模板了…

#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=3e4+10,M=2*N,mod=998244353,inf=2e9;

TP void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}

TP void qw(o x) {
	if(x<0) putchar('-'),x=-x;
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}

TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n,m,q,fa[N],top[N],tot,val[N],f[N][20],dep[N];
struct rec {
	int x,y,z;
	bool operator<(rec b) const {
		return z<b.z;
	}
} e[M];

struct edge{int y,next;}a[N]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}

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

void dfs(int x) {
	for(int k=last[x],y;k;k=a[k].next) {
		y=a[k].y;
		f[y][0]=x;dep[y]=dep[x]+1;
		for(int i=1;i<=16;i++) f[y][i]=f[f[y][i-1]][i-1];
		dfs(y);
	}
}

int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	for(int k=dep[x]-dep[y],i=0;k;i++)
		if(k>>i&1) x=f[x][i],k^=1<<i;
	for(int i=16;i>=0;i--)
		if(f[x][i]^f[y][i])
			x=f[x][i],y=f[y][i];
	return val[f[x][0]];
}

int main() {
	qr(n); qr(m); qr(q);
	for(int i=1;i<=m;i++) qr(e[i].x),qr(e[i].y),qr(e[i].z);
	sort(e+1,e+m+1);
	for(int i=1;i<=n;i++) fa[i]=top[i]=i;
	tot=n;
	for(int i=1;i<=m;i++) if(get(e[i].x)^get(e[i].y)) {
		int u=get(e[i].x),v=get(e[i].y);
		fa[u]=v; ++tot; ins(tot,top[u]),ins(tot,top[v]);
		top[v]=tot; val[tot]=e[i].z;
	}
	dfs(tot);
	while(q--) {
		int x,y; qr(x); qr(y);
		pr2(lca(x,y));
	}
	return 0;
}

练习:

BZOJ #3545. [ONTAK2010]Peaks

N N N座山峰,每座山峰有他的高度 h i h_i hi。有些山峰之间有双向道路相连,共 M M M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有 Q Q Q组询问,每组询问询问从点v开始只经过困难值小于等于 x x x的路径所能到达的山峰中第 k k k高的山峰,如果无解输出 − 1 。 -1。 1

首先建出 K r u s k a l Kruskal Kruskal重构树,我们倍增找到对应的子树后,那么就是子树内第 k k k大数.
这个用线段树合并可以简单处理.

#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=2e5+10,M=5e5+10,mod=998244353,inf=1e9;

TP void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}

TP void qw(o x) {
	if(x<0) putchar('-'),x=-x;
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}

TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n,m,q,f[N][22],fa[N],top[N],dep[N],val[N],tot;
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
struct rec {
	int x,y,z;
	bool operator <(rec b) const {
		return z<b.z;
	}
} e[M];

struct edge{int y,next;} a[N]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}

struct node{int l,r,c;} tr[N*30]; int cnt,root[N];
#define lc tr[x].l
#define rc tr[x].r
void update(int &x,int l,int r,int pos) {
	if(!x) x=++cnt;
	tr[x].c++;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(pos<=mid) update(lc,l,mid,pos);
	else update(rc,mid+1,r,pos);
}

int merge(int x,int y) {
	if(!x||!y) return x|y;
	int z=++cnt;
	tr[z].l=merge(lc,tr[y].l);
	tr[z].r=merge(rc,tr[y].r);
	if(!tr[z].l&&!tr[z].r) tr[z].c=tr[x].c+tr[y].c;
	else tr[z].c=tr[tr[z].l].c+tr[tr[z].r].c;
	return z;
}

void solve(int x,int k) {
	int l=0,r=inf,mid;
	if(k>tr[x].c) puts("-1");
	else {
		while(l<r) {
			mid=(l+r)/2;
			if(tr[rc].c<k) k -= tr[rc].c,x=lc,r=mid;
			else x=rc,l=mid+1;
		}
		pr2(l);
	}
}

void dfs(int x) {
	for(int k=last[x],y;k;k=a[k].next) {
		y=a[k].y;dep[y]=dep[x]+1;
		f[y][0]=x; for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
		dfs(y);
	}
}

int jump(int x,int k) {
	for(int i=17;i>=0;i--)
		if(val[f[x][i]]<=k)
			x=f[x][i];
	return x;
}


int main() {
	qr(n); qr(m); qr(q); tot=n;
	for(int i=1,x;i<=n;i++) fa[i]=top[i]=i,qr(x),update(root[i],0,inf,x);
	for(int i=1;i<=m;i++) qr(e[i].x),qr(e[i].y),qr(e[i].z);
	sort(e+1,e+m+1);
	for(int i=1,x,y;i<=m;i++)
		if((x=get(e[i].x))^(y=get(e[i].y))) {
			val[++tot]=e[i].z;
			ins(tot,top[x]); ins(tot,top[y]);
			root[tot]=merge(root[top[x]],root[top[y]]);
			fa[x]=y; top[y]=tot;
		}
	val[0]=2e9; dfs(tot);
	for(int i=1,x,y,z;i<=q;i++) {
		qr(x); qr(y); qr(z);
		solve(root[jump(x,y)],z);
	}
	return 0;
}


Luogu P4768 [NOI2018]归程

有棵 n n n个点, m m m条边的连通图,每条边有 l , h l,h l,h长度,海拔两个信息.
你将乘不能经过积水的车回1号点.
某天下雨了,你从 x x x号点回家, y y y及以下海拔的边将会被水淹,形成积水.
这种情况你只能下车走回家,作为一名肥宅,你必须想方设法最小化步行路程,不然你就不能及时赶回家看番了.

首先海拔越高的边越好,那么我们可以建一棵以 h h h为标准的最大生成树对应的重构树.
那么,如果 x x x能跳到的最高点(满足 y y y限制)对应的整个子树到1的最小距离即为答案.

所以,先从1号点 d i j k s t r a dijkstra dijkstra一遍,然后重构树即可.

#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
#define pb push_back
#define IT iterator
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=4e5+10,M=N*2,size=1<<20,mod=998244353,inf=2e9;

template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,fa[N],top[N],f[N][22],val[N],tot;

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

struct rec {
	int x,y,z;
	bool operator <(rec b) const {
		return z>b.z;
	}
} e[M];

struct edge{int y,next,d;}a[M]; int len,last[N];
void ins(int x,int y,int z=0) {a[++len]=(edge){y,last[x],z}; last[x]=len;}

void init() {
	memset(f+1,0,sizeof(f[0])*2*n);
	for(int i=1;i<=n;i++) {
		fa[i]=top[i]=i;
		last[i]=0;
	}
	tot=n; len=0;
}

int d[N];

void dijkstra() {
	priority_queue<pi> q;
	memset(d+1,63,sizeof(int[n]));
	d[1]=0; q.push(mk(0,1));
	while(!q.empty()) {
		int D=-q.top().fi,x=q.top().se; q.pop();
		if(D^d[x]) continue;
		for(int k=last[x],y,z;k;k=a[k].next) {
			y=a[k].y; z=a[k].d;
			if(d[y]>d[x]+a[k].d) {
				d[y]=d[x]+a[k].d;
				q.push(mk(-d[y],y));
			}
		}
	}
}

void dfs(int x) { 
	if(last[x]) d[x]=inf;//子树内最小距离 
	for(int k=last[x],y;k;k=a[k].next) {
		y=a[k].y;
		f[y][0]=x;
		for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
		dfs(y); d[x]=min(d[x],d[y]);
	}
}

void kruskal() {
	sort(e+1,e+m+1); val[0]=-1;
	len=0; memset(last+1,0,sizeof(int[2*n]));
	for(int i=1,x,y;i<=m;i++)
		if((x=get(e[i].x))^(y=get(e[i].y))) {
			val[++tot]=e[i].z;
			ins(tot,top[x]); ins(tot,top[y]);
			fa[x]=y; top[y]=tot;
		}
	dfs(tot);
}

int solve(int x,int k) {
	for(int i=18;i>=0;i--) 
		if(val[f[x][i]]>k)
			x=f[x][i];
	return d[x];
}

int main() {
	int _;qr(_); while(_--) {
		qr(n); qr(m); init();
		for(int i=1,x,y,l,h;i<=m;i++) {	
			qr(x); qr(y); qr(l); qr(h);
			e[i]=(rec){x,y,h};
			ins(x,y,l); ins(y,x,l);
		}
		dijkstra();
		kruskal();
		int q,k,s,x,y,ans=0;
		qr(q); qr(k); qr(s);
		while(q--) {
			qr(x); qr(y);
			x=(x+k*ans-1)%n+1;
			y=(y+k*ans)%(s+1);
			ans=solve(x,y);
			pr2(ans);
		}
	}
	return 0;
}

Luogu P4899 [IOI2018] werewolf 狼人

我们以点权从大到小和从小到大依次构树,得到 A , B A,B A,B.

A往上跳即可得到人的可达位置.
B往上跳即可得到狼人的可达位置.

此时我们判断一下 A , B A,B A,B是否可交即可,这个我们对于每个B中的节点维护子树内 A A A d f s dfs dfs序集合即可.

#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define fi first
#define se second
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=2e5+10,M=8e5+10,mod=998244353,inf=2e9;

TP void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}

TP void qw(o x) {
	if(x<0) putchar('-'),x=-x;
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}

TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n,m,q;
vector<int> e[N];

struct Kruskal {
	bool flag;
	int fa[N],tot,in[N],out[N],b[N],f[N][22];
	int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
	struct edge{int y,next;}a[N*2]; int len,last[N];
	void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}
	
	void dfs(int x) {
		in[x]=++tot; b[tot]=x;
		for(int k=last[x],y;k;k=a[k].next) {
			y=a[k].y; f[y][0]=x;
			for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
			dfs(y);
		}
		out[x]=tot;
	}
	
	void init(bool v) {//true为人的限制,以点权建立小根树
		for(int i=1;i<=n;i++) fa[i]=i; 
		flag=v;
		if(v) {
			for(int i=n; i;i--)
				for(int y:e[i]) {
					int u=get(i),v=get(y);
					if(i<y&&u^v) {
						ins(u,v);
						fa[v]=u;
					}
				}
		}
		else {
			for(int i=1;i<=n;i++)
				for(int y:e[i]) {
					int u=get(i),v=get(y);
					if(i>y&&u^v) {
						ins(u,v);
						fa[v]=u;
					}
				}
		}
		dfs(get(1));
	}
	
	pi solve(int x,int k) {
		if(flag) {
			if(x<k) return mk(0,0);
			for(int i=18;i>=0;i--)
				if(f[x][i]>=k)
					x=f[x][i];
		}
		else {
			if(x>k) return mk(0,0);
			for(int i=18;i>=0;i--)
				if(f[x][i]&&f[x][i]<=k)
					x=f[x][i];
		}
		return mk(in[x],out[x]);
	}
} a,b;

struct node {int l,r,c;} tr[N*40]; int tot,root[N];
#define lc tr[x].l
#define rc tr[x].r
void update(int &x,int y,int l,int r,int pos) {
	tr[x=++tot]=tr[y]; tr[x].c++;
	if(l==r) return ;;
	int mid=(l+r)/2;
	if(pos<=mid) update(lc,tr[y].l,l,mid,pos);
	else update(rc,tr[y].r,mid+1,r,pos);
}

void bt() {
	for(int i=1;i<=n;i++) 
		update(root[i],root[i-1],1,n,a.in[b.b[i]]);
}

bool ask(int x,int y,int l,int r,int L,int R) {
	if(tr[x].c==tr[y].c) return 0;
	if(L<=l&&r<=R) return 1;
	int mid=(l+r)/2,res=0;
	if(L<=mid) res = ask(lc,tr[y].l,l,mid,L,R);
	if(mid< R&&!res) res = ask(rc,tr[y].r,mid+1,r,L,R);
	return res;
}

int main() {
	qr(n); qr(m); qr(q);
	for(int i=1,x,y;i<=m;i++)
		qr(x),qr(y),++x,++y,e[x].push_back(y),e[y].push_back(x);
	a.init(1); b.init(0); bt();
	while(q--) {
		int x,y,l,r; qr(x); qr(y); qr(l); qr(r);
		x++; y++; l++; r++;
		pi u=a.solve(x,l),v=b.solve(y,r);
		if(u.fi&&v.fi) pr2(ask(root[v.se],root[v.fi-1],1,n,u.fi,u.se));
		else puts("0");
	}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值