并查集 hdoj1232 NOIP 2010

1.hdoj 1232

http://acm.hdu.edu.cn/showproblem.php?pid=1232

首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。像畅通工程这题,问还需要修几条路,实质就是求有几个连通分支。如果是1个连通分支,说明整幅图上的点都连起来了,不用再修路了;如果是2个连通分支,则只要再修1条路,从两个分支中各选一个点,把它们连起来,那么所有的点都是连起来的了;如果是3个连通分支,则只要再修两条路……

int find(int i){
	int root=i;
	while(root!=pre[root]){
	int before=root;
	root=pre[root];
	pre[before]=root;//包含路径压缩
	}
	return root;
}
void join(int p,int q){
	int rootp=find(p);
	int rootq=find(q);
	if(sz[p]>sz[q]){
	pre[rootq]=rootp;
	sz[p]+=sz[q];
	}
	else{
	pre[rootp]=rootq;
	sz[q]+=sz[p];
	}
}

为了解释并查集的原理,我将举一个更有爱的例子。 话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。


<pre name="code" class="cpp">#include <cstdio>
#include <iostream>
using namespace std;
const int maxn=1050;
int pre[maxn];
int sz[maxn];

int find(int i){
	int root=i;
	while(root!=pre[root]){
		int before=root;
		root=pre[root];
		pre[before]=root;
	}
	return root;
}

void join(int a,int b){
	int roota=find(a);
	int rootb=find(b);
	if(roota==rootb) return;
	// pre[roota]=rootb;
	 if(sz[a]>sz[b]){
	 	pre[rootb]=roota;
		sz[roota]+=sz[rootb];
	}
	else{
		pre[roota]=rootb;
		sz[rootb]+=sz[roota];
	}
}
int main(){
	int n,m;
	while(cin>>n>>m){
		if(n==0) break;
		for(int i=1;i<=n;i++){
			pre[i]=i;
			sz[i]=1;
		}
		int a,b;
		for(int i=0;i<m;i++){
			scanf("%d %d",&a,&b);
			join(a,b);
		}
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(pre[i]==i) cnt++;
		}
		cout<<cnt-1<<endl;
	}
	return 0;
}


 

isroot可以省略,只要判断if(pre[i]==i)就行了

有两个注意:

1.sz其实不是必要的,因为路径压缩已经能保证查找完几乎是扁平的树了。

2.find中的路径压缩其实效率不高,应该先找到根节点再逐渐压缩。

改进的find算法

int find(int p){
	int root=p;
	while(root!=pre[root]){
		root=pre[root];
	}
	while(p!=root){
		int next=pre[p];
		pre[p]=root;
		p=next;
	}
	return root;
}



NOIP2010提高组第三题

我们使用一种贪心的想法(努力避免最大矛盾),先按照仇恨排序。把仇恨最大的两个人当做父节点,然后逐渐把其他边和父节点相连,一旦发现有冲突。就输出。显然这种贪心的方式是正确的(让冲突在仇恨最小的时候发生)


#include <iostream>
using namespace std;
typedef struct edge{
	int f,t,c;
}E;
int pre[20020];
E Edges[40001];

bool cmp(edge a,edge b){
	return a.c>b.c;
}
int find(int p){
	while(p!=pre[p]){
		p=pre[p];
	}
	return p;
}
int n,m;
int main(int argc, char const *argv[])
{
	cin>>n>>m;
	int a,b,c;
	for(int i=0;i<m;i++){
		cin>>Edges[i].f>>Edges[i].t>>Edges[i].c;
	}
	sort(Edges,Edges+m,cmp);
	int roota=Edges[0].f;
	int rootb=Edges[0].t;
	for(int i=1;i<=n;i++) pre[i]=i;
	pre[roota]=roota; 
	pre[rootb]=rootb;
	bool flag=true;
	for(int i=1;i<m;i++){
		int a=find(Edges[i].f);
		int b=find(Edges[i].t);
		if(a==b){
			printf("%d\n", Edges[i].c);
			flag=false;
			break;
		}
		pre[a]=roota;
		pre[b]=rootb;
	}
	if(flag) printf("0\n");
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值