带权并查集初步

简单学习了一下带权并查集,参考了这个博客,感觉写的不错:https://blog.csdn.net/yjr3426619/article/details/82315133

和一般的并查集相比,代码的变化主要在路径压缩和合并的时候。因为带上了权值,所以路径压缩的时候要更新权值(一般v[x]表示的都是x与par[x]之间的权值,需要更新成x与x的根之间的权值)。然后合并的时候,找到x和y的根px和py,将px连接向py(或py连向px)的时候,也要算出两者的权值。具体的更新代码和图看上面的博客就好,讲的挺清楚。

下面记录三道题目吧,自己实现了博客里的例题。

hdu 3038:题目链接:https://vjudge.net/problem/HDU-3038

大意就是给你一系列区间和,判断有多少个和之前给定的矛盾(如果是矛盾就跳过)。可以用带权并查集来做:把结点看做区间的端点,两个结点之间的权值看做这个区间内的和。当前给定的两个结点属于同一个集合时,可以求出它们的区间和,然后判断是否和给定的值相等,如果不相等则ans++;若不属于同一个集合,就把它们对应的根合并,并求出两个根之间的权值。基本是模板题,但有一个点要注意:每次读入的时候要把左边端点当做开区间(使得左边端点-1即可)。举例:比如区间[1,4],读入了[1,2]和[1,4]后就可以确定[3,4],但是实际执行的时候会把3,4当做在不同的集合。但是改成(0,2]和(2,4]就不会有这个问题了,此时读入[3,4]相当于求(2,4],2和4属于同一个集合。

代码:

#include<cstdio>
#include<cstring>
using namespace std;

const int maxn=200+10;
int par[maxn],v[maxn];
int n,m,xx,x,y,t,i,j;

int find(int x){ //带权路径压缩 
	if (par[x]==x) return x;
	else{
		int p=par[x];
		par[x]=find(par[x]);
		v[x]+=v[p];
		return par[x];
	}
}

int main(){
	//freopen("hdu3038.txt","r",stdin);
	while (~scanf("%d%d",&n,&m)){
		int ans=0;
		for (i=0;i<=n;i++) par[i]=i;
		memset(v,0,sizeof(v));
		for (i=1;i<=m;i++){
			scanf("%d%d%d",&xx,&y,&t);
			x=xx-1; //
			int px=find(x);int py=find(y);
			if (px==py){
				int d=v[x]-v[y];  //***
				if (d!=t) ans++;
			} 
			else{
				par[px]=py;
				v[px]=v[y]-v[x]+t; //***
			}
		}
		printf("%d\n",ans);
	}
	//fclose(stdin);
	return 0;
}

  

hihocoder1515

题目链接:https://vjudge.net/problem/HihoCoder-1515

水题,设v[x]表示x比x当前的父亲par[x]低多少分就行了(我觉得设低多少分舒服一些......

代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=100+10;
int par[maxn],v[maxn]; //v[x]表示x比par[x]低多少分 
int s,n,m,q,t,i,j,x,y;

int find(int x){
	if (par[x]==x) return x;
	else{
		int p=par[x];
		par[x]=find(par[x]);
		v[x]+=v[p];
		return par[x]; 
	}
}

int main(){
	//freopen("hiho1515.txt","r",stdin);
	scanf("%d%d%d",&n,&m,&q);
	for (i=1;i<=n;i++) par[i]=i;
	memset(v,0,sizeof(v));
	for (i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&s);s=-s;
		int px=find(x);int py=find(y);
		if (px!=py){
			par[px]=py;
			v[px]=v[y]-v[x]+s;
		}
	}
	for (i=1;i<=q;i++){
		scanf("%d%d",&x,&y);
		int px=find(x);int py=find(y);
		if (px!=py) printf("-1\n");else printf("%d\n",v[y]-v[x]);
	}
	//fclose(stdin);
	return 0;
}

  

poj1182 食物链

题目链接:https://vjudge.net/problem/POJ-1182

这题应该算是经典题了,同时也有点变化。自己脑补了一会成功弄出来了(主要是看到了博客里一个模3的提示......)

设v[x]=0表示x和par[x]是同类,v[x]=1表示x吃par[x],v[x]=2表示x被par[x]吃。更新的时候代码要做相应的变化。一是路径压缩的时候v[x]=(v[x]+v[p])%3,二是求x和y的权值t,t=(v[x]-v[y]+3)%3,三是合并px和py,求px和py之间的权值的时候,v[px]=(v[y]-v[x]+t+3)%3。

上个代码吧:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=500+10;
int par[maxn],v[maxn];  //v[x]=0:xºÍpar[x]ͬÀà  v[x]=1:x³Ôpar[x]  v[x]=2:x±»par[x}³Ô 
int t,i,j,k,n,m,d,x,y,ans;

int find(int x){
	if (par[x]==x) return x;
	else{
		int p=par[x];
		par[x]=find(par[x]);
		v[x]=(v[p]+v[x])%3;  //* 
		return par[x];
	}
}

int main(){
	//freopen("poj1182.txt","r",stdin);
	scanf("%d%d",&n,&k);
	for (i=1;i<=n;i++) par[i]=i;
	memset(v,0,sizeof(v));
	ans=0;
	for (i=1;i<=k;i++){
		scanf("%d%d%d",&d,&x,&y);
		if (x<1||x>n||y<1||y>n) {
		    ans++;continue;	
		}
		int px=find(x);int py=find(y);
		if (px==py){
			t=(v[x]-v[y]+3)%3; //*
			if (d==1&&t!=0) ans++;
			if (d==2&&t!=1) ans++;
		}
		else{
			t=d-1; //* 
			par[px]=py;
			v[px]=(v[y]-v[x]+t+3)%3; //*
		}
	}
	printf("%d\n",ans);
	//fclose(stdin);
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/edmunds/p/12524390.html