克鲁斯卡尔重构树

克鲁斯卡尔重构树

用途

用来维护图上两点, u u u v v v的路径中,最大边权的最小值(或最小边权的最大值)

实现

像克鲁斯卡尔一样,先把边权排序,然后用并查集 建出一颗重构树,点权就是当前边的边权,然后再用树链剖分dfs两次,方便之后求 l c a lca lca
如果原图不是连通图,那么建出的就是重构森林,以每个连通块最后加入的点为根;如果原图是连通图,那么最后加入的节点,就是重构树的根。

代码

void work(){
	cnt=n;
	for(int i=1;i<=n;i++)ff[i]=i;
	sort(z+1,z+1+m,cmp);
	for(int i=1;i<=m;i++){
		int a=find(z[i].x);
		int b=find(z[i].y);
		if(a==b)continue;
		val[++cnt]=z[i].w;
		ff[cnt]=ff[a]=ff[b]=cnt;
		v[cnt].push_back(a);
		v[cnt].push_back(b);
		v[a].push_back(cnt);
		v[b].push_back(cnt);
	}
	for(int i=1;i<=cnt;i++){
		if(vis[i])continue;
		int a=find(i);
		dfs1(a,0);dfs2(a,0);
	}
	/*
	//如果图是联通的,那么只需要从最后一个点开始处理即可
	dfs1(cnt,0);dfs2(cnt,0); 
	*/
}

例题

DarkOJ 3732

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

int n,m,q;
struct node{
	int x,y,w;
}z[30040];
bool cmp(node a,node b){
	return a.w<b.w;
}
int ff[30050],fa[30050],siz[30050],son[30050],dep[30050],top[30050];
int val[30050],vis[30050],cnt;
vector<int>v[30050];
int find(int x){
	if(x==ff[x])return x;
	return ff[x]=find(ff[x]);
}
void dfs1(int x,int pre){
	fa[x]=pre;dep[x]=dep[pre]+1;
	siz[x]=1;vis[x]=1;
	for(int i=0;i<v[x].size();i++){
		int to=v[x][i];
		if(to==pre)continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]])son[x]=to;
	}
}
void dfs2(int x,int pre){
	if(son[pre]==x)top[x]=top[pre];
	else top[x]=x;
	if(son[x])dfs2(son[x],x);
	for(int i=0;i<v[x].size();i++){
		int to=v[x][i];
		if(to==pre||to==son[x])continue;
		dfs2(to,x);
	}
}
void work(){
	cnt=n;
	for(int i=1;i<=n;i++)ff[i]=i;
	sort(z+1,z+1+m,cmp);
	for(int i=1;i<=m;i++){
		int a=find(z[i].x);
		int b=find(z[i].y);
		if(a==b)continue;
		val[++cnt]=z[i].w;
		ff[cnt]=ff[a]=ff[b]=cnt;
		v[cnt].push_back(a);
		v[cnt].push_back(b);
		v[a].push_back(cnt);
		v[b].push_back(cnt);
	}
	for(int i=1;i<=cnt;i++){
		if(vis[i])continue;
		int a=find(i);
		dfs1(a,0);dfs2(a,0);
	}
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&z[i].x,&z[i].y,&z[i].w);
	}
	work();
	for(int i=1;i<=q;i++){
		int a,b;scanf("%d%d",&a,&b);
		printf("%d\n",val[lca(a,b)]);
	}
	return 0;
}

DarkOJ 3551

题意:

给你一张 n n n个点, m m m条边的图,在线询问 q q q组,每次询问从 v v v作为出发点,只能经过边权小于等于 x x x的边,求点权第 k k k大的点。

思路:

首先当然是,克鲁斯卡尔重构一下,这样只要从 v v v开始,找一个最浅的祖先 y y y,满足权值小于等于 x x x
那么 y y y的子树包含的叶子点,就是满足条件的集合。
那么问题就转变成了,求这个集合第 k k k大;
那么维护子树信息,能用什么数据结构呢:
主席树+dfs序,那么子树就是线性的一段,直接查就行;

(作死想到了线段树合并
可惜无了,线段树合并貌似只能维护离线询问,在所有都合并了之后,权值线段树的结构就变了,如果万能的友友 有能够处理的方法,评论区告诉我~

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

int n,m,q;
struct node{
	int x,y,w;
}z[500040];
int h[100050];
int hh[100050];
int len=0;
int val[200050],ff[200050],fa[200050][20],dep[200050],siz[200050];
int cnt;
vector<int>v[200050];
int tot=0;
int root[200050];
int ls[200050],rs[200050];
struct nod{
	int l,r,num;
}zz[4000050];

bool cmp(node a,node b){
	return a.w<b.w;
}
int clone(int x){
	tot++;
	zz[tot]=zz[x];
	return tot;
} 
void update(int id1,int &id2,int l,int r,int x){
	id2=clone(id1);zz[id2].num++;
	if(l>=r)return ;
	int mid=l+r>>1;
	if(x<=mid)update(zz[id1].l,zz[id2].l,l,mid,x);
	else update(zz[id1].r,zz[id2].r,mid+1,r,x);
}
int query(int id1,int id2,int l,int r,int k){
	if(l>=r)return l;
	int mid=l+r>>1;
	int num=zz[zz[id2].r].num-zz[zz[id1].r].num;
	if(k<=num)return query(zz[id1].r,zz[id2].r,mid+1,r,k);
	else return query(zz[id1].l,zz[id2].l,l,mid,k-num);
}
int num=0;

int find(int x){
	if(x==ff[x])return x;
	return ff[x]=find(ff[x]);
}
void dfs(int x,int pre){
	fa[x][0]=pre;dep[x]=dep[pre]+1;
	siz[x]=1;
	for(int i=1;i<20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	int f=0;
	ls[x]=1e9,rs[x]=0;
	for(int i=0;i<v[x].size();i++){
		int to=v[x][i];
		if(to==pre)continue;
		dfs(to,x);
		siz[x]+=siz[to];
		ls[x]=min(ls[x],ls[to]);
		rs[x]=max(rs[x],rs[to]);
		f=1;
	}
	if(!f){
		int pos=lower_bound(hh+1,hh+1+len,h[x])-hh;
		num++;
		update(root[num-1],root[num],1,len,pos);
		ls[x]=rs[x]=num;
	}
}
void work(){
	cnt=n;
	for(int i=1;i<=n;i++)ff[i]=i;
	sort(z+1,z+1+m,cmp);
	for(int i=1;i<=m;i++){
		int a=find(z[i].x);
		int b=find(z[i].y);
		if(a==b)continue;
		val[++cnt]=z[i].w;
		ff[cnt]=ff[a]=ff[b]=cnt;
		v[cnt].push_back(a);
		v[cnt].push_back(b);
		v[a].push_back(cnt);
		v[b].push_back(cnt);
	}
	dfs(cnt,0);
}
int find(int x,int y){
	val[0]=1e9+7;
	for(int i=19;i>=0;i--){
		if(val[fa[x][i]]<=y)x=fa[x][i];
	}
	return x;
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&h[i]);
		hh[i]=h[i];
	}
	sort(hh+1,hh+1+n);
	len=unique(hh+1,hh+1+n)-hh-1;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&z[i].x,&z[i].y,&z[i].w);
	}
	work();
	int ans=0;
	for(int i=1;i<=q;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		a=a^ans;b^=ans;c^=ans;
		a=find(a,b);
		if(zz[root[rs[a]]].num-zz[root[ls[a]-1]].num<c)printf("-1\n"),ans=0;
		else {
			ans=hh[query(root[ls[a]-1],root[rs[a]],1,len,c)];
			printf("%d\n",ans);
		}
	}
	return 0;
}

P4768 [NOI2018] 归程
也是一道比较模板的题目吧

平平无奇的AC~

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
typedef pair<int,int> PII;

int n,m,Q,t,K,S;

struct node{
	int x,y,w;
}z[400050];
bool cmp(node a,node b){
	return a.w>b.w;
}
int len=0,head[400050],dis[400050],vis[400050];
priority_queue<PII,vector<PII>,greater<PII>> heap;

int cnt,ff[800050],fa[800050][22];
int len1=0,head1[800050];
int val[800060];
int ans[800060];

int find(int x){
	if(x==ff[x])return x;
	return ff[x]=find(ff[x]);
}

struct edge{
	int e,w,nex;
}ed[800050],ed1[1600050];
void add(int a,int b,int l){
	ed[len].e=b;
	ed[len].w=l;
	ed[len].nex=head[a];
	head[a]=len++;
	
}
void add1(int a,int b){
	ed1[len1].e=b;
	ed1[len1].nex=head1[a];
	head1[a]=len1++;
}
void init(){
	len=0;len1=0;
	for(int i=1;i<=n;i++)head[i]=-1,dis[i]=1e9+7,vis[i]=0;
	for(int i=1;i<=2*n;i++)head1[i]=-1,val[i]=0;
}
void work1(){
	dis[1]=0;
	heap.push({0,1});
	while(heap.size()){
		PII t=heap.top();heap.pop();
		int ver=t.second,dist=t.first;
		if(vis[ver])continue;
		vis[ver]=1;
		for(int i=head[ver];i!=-1;i=ed[i].nex){
			int j=ed[i].e;
			if(dis[j]>dist+ed[i].w){
				dis[j]=dist+ed[i].w;
				heap.push({dis[j],j});
			}
		}
	}
}
void dfs(int x,int pre){
	fa[x][0]=pre;ans[x]=1e9+7;
	for(int i=1;i<=20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	int f=0;
	for(int i=head1[x];i!=-1;i=ed1[i].nex){
		int to=ed1[i].e;
		if(to==pre)continue;
		dfs(to,x);
		f=1;
		ans[x]=min(ans[x],ans[to]);
	}
	if(!f)ans[x]=dis[x];
}
void work2(){
	cnt=n;
	for(int i=1;i<=n;i++)ff[i]=i;
	sort(z+1,z+1+m,cmp);
	for(int i=1;i<=m;i++){
		int a=find(z[i].x);
		int b=find(z[i].y);
		if(a==b)continue;
		val[++cnt]=z[i].w;
		ff[cnt]=ff[a]=ff[b]=cnt;
		add1(cnt,a);add1(a,cnt);
		add1(cnt,b);add1(b,cnt);
	}
	dfs(cnt,0);
}
int find(int x,int y){
	for(int i=20;i>=0;i--){
		if(val[fa[x][i]]>y)x=fa[x][i];
	}
	return x;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<=m;i++){
			int a,b,c,d;
			scanf("%d%d%d%d",&a,&b,&c,&d);
			add(a,b,c);add(b,a,c);
			z[i].x=a;z[i].y=b;z[i].w=d;
		}
		work1();
		work2();
		scanf("%d%d%d",&Q,&K,&S);
		int res=0;
		for(int i=1;i<=Q;i++){
			int a,b;
			scanf("%d%d",&a,&b);
			a=(a+K*res-1)%n+1;
			b=(b+K*res)%(S+1);
			res=ans[find(a,b)];
			printf("%d\n",res);
		}
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值