并查集(disjoint set)
不相交集合,任何两个集合做交集都为空
操作
1.合并两个集合
Mergel(a,b)
{
i=min(a,b);
j=max(a,b);
for(k=1;k<=n;k++)
{
if(Set[k]==j)
Set[k]=i;
}
}
改进:树状结构
每个集合用一颗“有根树”表示
Set[i] = i, 则i表示本集合,并是集合对应树的根
Set[i] = j, 若j不等于i,则是i的父节点
查找
find2(x)
{
r=x;
while(Set[r]!=r)
r=Set[r];
return r;
}
合并
merge2(a,b)
{
Set[a]=b;
}
2.查找某元素属于哪个集合
findl(x)
{
return Set[x];
}
方法:
将深度小的树合并到深度大的数
merge3(a,b)
{
if(height(a)==height(b))
{
height(a)=height(a)+1;
Set[b]=a;
}else if(height(a)<height(b))
Set[a]=b;
else Set[b]=a;
}
经典应用--最小生成树
树:n个顶点,n-1条边
最小生成树:边的权值和最小
理论基础:MST性质
克鲁斯卡尔算法
结合贪心算法。
1.畅通工程
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入描述
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的。
当N为0时,输入结束,该用例不被处理。输出描述
对每个测试用例,在1行里输出最少还需要建设的道路数目。
用例输入 1
4 2 1 3 4 3 3 3 1 2 1 3 2 3 5 2 1 2 3 5 999 0 0用例输出 1
1 0 2 998
#include<bits/stdc++.h>
using namespace std;
int bin[1005];
//查找
int find(int x)
{
int r=x;
while(bin[r]!=r)
r=bin[r];
return r;
}
//合并
void merge(int x,int y)
{
int fx,fy;
fx=find(x);
fy=find(y);
if(fx!=fy)
bin[fx]=fy;
}
int main()
{
int n,m,i,x,y,count;
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
//初始化,每个人都是老大
for(i=1;i<=n;i++) bin[i]=i;
cin>>m;
for( ;m>0;m--)
{
cin>>x>>y;
merge(x,y);//合并
}
//找有几个老大
count=-1;//修的路得数量为集合数量减一
for(i=1;i<=n;i++)
{
if(bin[i]==i) count++;
}
cout<<count<<endl;
}
return 0;
}
2.还是畅通工程
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入描述
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。输出描述
对每个测试用例,在1行里输出最小的公路总长度。
用例输入 1
3 1 2 1 1 3 2 2 3 4 4 1 2 1 1 3 4 1 4 1 2 3 3 2 4 2 3 4 5 0用例输出 1
3 5提示
Huge input, scanf is recommended.
#include<bits/stdc++.h>
using namespace std;
int mark[110];
//
struct node{
int u,v,w;
}edge[99999];
//查找
int find(int x)
{
return mark[x]==x?x:mark[x]=find(mark[x]);
}
//从小到大
int cmp(node a,node b)
{
return a.w<b.w;
}
int main()
{
int n,i,m,sum,c,d;
while(cin>>n&&n)
{
for(i=1;i<=n;i++) mark[i]=i;
m=n*(n-1)/2;
sum=0;
//村庄距离
for(i=1;i<=m;i++)
cin>>edge[i].u>>edge[i].v>>edge[i].w;
//排序
sort(edge+1,edge+m+1,cmp);
for(i=1;i<=m;i++)
{
//在集合里查找
c=find(edge[i].u);
d=find(edge[i].v);
//不在一个集合里,连接起来
if(c!=d)
{
mark[c]=d;
sum=sum+edge[i].w;
}
}
cout<<sum<<endl;
}
return 0;
}
3.畅通工程
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。
输入描述
测试输入包含若干测试用例。
每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N 行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。
为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。
输出描述
对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。
用例输入 1
3 3 1 2 1 1 3 2 2 3 4 1 3 2 3 2 0 100用例输出 1
3 ?
#include<bits/stdc++.h>
using namespace std;
int mark[110];
//
struct node{
int u,v,w;
}edge[99999];
//查找
int find(int x)
{
return mark[x]==x?x:mark[x]=find(mark[x]);
}
//从小到大
int cmp(node a,node b)
{
return a.w<b.w;
}
int main()
{
int n,i,m,cnt,sum,c,d;
while(cin>>n>>m&&n)
{
for(i=1;i<=m;i++) mark[i]=i;
cnt=1;
sum=0;
//村庄距离
for(i=1;i<=n;i++)
cin>>edge[i].u>>edge[i].v>>edge[i].w;
//排序
sort(edge+1,edge+n+1,cmp);
for(i=1;i<=n;i++)
{
//在集合里查找
c=find(edge[i].u);
d=find(edge[i].v);
//不在一个集合里,连接起来
if(c!=d)
{
mark[c]=d;
cnt++;
sum=sum+edge[i].w;
}
}
if(cnt==m) cout<<sum<<endl;
else cout<<"?"<<endl;
}
return 0;
}
4.继续畅通工程
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全省畅通需要的最低成本。
输入描述
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( 1< N < 100 );随后的 N(N-1)/2 行对应村庄间道路的成本及修建状态,每行给4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态:1表示已建,0表示未建。
当N为0时输入结束。
输出描述
每个测试用例的输出占一行,输出全省畅通需要的最低成本。
用例输入 1
3 1 2 1 0 1 3 2 0 2 3 4 0 3 1 2 1 0 1 3 2 0 2 3 4 1 3 1 2 1 0 1 3 2 1 2 3 4 1 0用例输出 1
3 1 0
#include<bits/stdc++.h>
using namespace std;
int mark[110];
struct node{
int u,v,w,flag;
}edge[99999];
//查找
int find(int x)
{
return mark[x]==x?x:mark[x]=find(mark[x]);
}
//从小到大
int cmp(node a,node b)
{
return a.w<b.w;
}
int main(){
int c,d,n,m,i,sum,num;
while(cin>>n&&n)
{
for(i=1;i<=n;++i) mark[i]=i;
m=n*(n-1)/2;
num=0;
sum=0;
for(i=1;i<=m;i++)
{
cin>>edge[i].u>>edge[i].v>>edge[i].w>>edge[i].flag;
if(edge[i].flag!=0)
{
c=find(edge[i].u);
d=find(edge[i].v);
if(c!=d)
mark[c]=d;
}
else edge[++num]=edge[i];
}
sort(edge+1,edge+num+1,cmp);
for(int i=1;i<=num;++i)
{
c=find(edge[i].u);
d=find(edge[i].v);
if(c!=d)
{
mark[c]=d;sum+=edge[i].w;
}
}
cout<<sum<<endl;
}
}
5.小希的迷宫
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走。
但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间A和B,那么既可以通过它从房间A走到房间B,也可以通过它从房间B走到房间A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。
小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从5到达8。
输入描述
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为1,且不超过100000。每两组数据之间有一个空行。
整个文件以两个-1结尾。
输出描述
对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出"Yes",否则输出"No"。
用例输入 1
6 8 5 3 5 2 6 4 5 6 0 0 8 1 7 3 6 2 8 9 7 5 7 4 7 8 7 6 0 0 3 8 6 8 6 4 5 3 5 6 5 2 0 0 -1 -1用例输出 1
Yes Yes No
#include<bits/stdc++.h>
using namespace std;
int mark[100008]={0};
//查找
int find(int x)
{
return mark[x]==x?x:mark[x]=find(mark[x]);
}
//合并
void fun(int x,int y)
{
int fx,fy;
fx=find(x);
fy=find(y);
if(fy!=fx) mark[fy]=fx;
}
int main()
{
int a,b,flag,i,k=1,sum;
while(1)
{
flag=0;
while((cin>>a>>b)&&a!=0&&b!=0)
{
if(a==-1||b==-1) return 0;
if(mark[a]==0) mark[a]=a;
if(mark[b]==0) mark[b]=b;
if(find(a)==find(b)) flag=1;
if(flag!=1) fun(a,b);
}
for(i=1,sum=0;i<=100008;i++)
{
if(mark[i]==i) sum++;
mark[i]=0;
}
if(sum>1||flag==1) printf("No\n");
else printf("Yes\n");
}
return 0;
}