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;
}