家族
Time Limit:10000MS Memory Limit:65536K
Total Submit:323 Accepted:155
Case Time Limit:1000MS
Description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
Input
第一行:三个整数n,m,p,(n<=50000,m<=50000,p<=50000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
Output
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
Sample Input
6 5 3
41 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
Sample Output
Yes
Yes
No
思路
并查集模板题:
将每个人抽象成为一个点,数据给出M个边的关系,两个人是亲戚的时候两点间有一条边。
很自然的就得到了一个N个顶点M条边的图论模型。在图中一个连通块中的任意点之间都是亲戚。
对于最后的Q个提问,即判断所提问的两个顶点是否在同一个连通块中。
路径压缩
为了进一步增强堆寻找运算性能,路径压缩的措施被使用。在FIND(x)运算中,找到根节点y后,
我们再进行一次遍历从x到y的路径,并沿着路径改变所有节点指向父节点的指针,使它直接指向y。如下图:
int find(int x)
{
if(x==fa[x]) return x;
fa[x]=find(fa[x]);
return fa[x];
}
输入边时,将边连接的两个端点的连通块连起来,即
合并集合
包含元素x和y的两个集合用它们的并集替换。并集的名字或者是原来的包含元素x那个集合的名字,
或者是原来包含元素y的那个集合的名字。
对于每个询问x、y,若xy的根节点相同,则属于相同连通块,则是亲戚。
并查集的存储结构:
数组fa[x]表示结点x的根节点,如上图。
void un(int x,int y)
{
int fx=find(x),fy=find(y);
fa[fy]=fx;
}
代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
long long fa[50001],n,m,x,p,y;
int find(int x)//压缩路径
{
if(x==fa[x]) return x;
fa[x]=find(fa[x]);//递归,寻找根节点的同时压缩路径
return fa[x];
}
void un(int x,int y) //合并集合
{
int fx=find(x),fy=find(y);
fa[fy]=fx;
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++)
{
fa[i]=i;//将每个节点的根初始化为本身
}for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);//输入边
un(x,y);//将边连接的两个端点的连通块连起来
}for(int i=1;i<=p;i++){
scanf("%d%d",&x,&y);
if(find(x)!=find(y)) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
//若xy的根节点相同则是亲戚,反之······
}
}