并查集总结篇
Posted MissZhou要努力
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集总结篇相关的知识,希望对你有一定的参考价值。
1、模板题 poj1611the suspects
每个组内的人,同一个组内都是感染者,问与“0”号人有关的有多少人
#include <iostream>
#include<cstdio>
using namespace std;
const int MAXN = 1000100;
struct DS
{
int f[MAXN];
void init(int n)
{
for(int i=0;i<n;i++)
f[i]=i;
}
int ff(int x) ///int father (a);
{
///查找父亲并压缩路径
if(f[x]!=x)
f[x]=ff(f[x]);
return f[x];
}
void join(int a,int b) ///void union (a, b);
{
f[ff(a)]=f[ff(b)];
}
int find(int a,int b)
{
return ff(a)==ff(b);
}
}soul;
int main()
{
// freopen("cin.txt","r",stdin);
int n,m,a,b,c,t,num[100000];
while(cin>>n>>m)
///n:人数 m:操作数
{
if(m==0&&n==0) break;
soul.init(n);
while(m--)
{
cin>>t;
for(int i=1;i<=t;i++) cin>>num[i];
for(int i=1;i<t;i++)
soul.join(num[i],num[i+1]);
}
int q=soul.ff(0);
int count=1;
for(int i=1;i<n;i++)
if(q==soul.ff(i)) count++;
cout<<count<<endl;
}
return 0;
}
2、初级带权并查集 poj2492bug's life
题意:给出每对虫子的互相吸引关系,只有异性才会相互吸引,问虫子当中有没有同性恋==
解决方法:添加一个数组表示每个虫子的性别,注释加讲解
/*
关于并查集,注意两个概念:按秩合并、路径压缩。
1、按秩合并
由于并查集一般是用比较高效的树形结构来表示的,按秩合并的目的就是防止产生退化的树(也就是类似链表的树),
用一个数组记录各个元素的高度(也有的记录各个元素的孩子的数目,具体看哪种能给解题带来方便),
然后在合并的时候把高度小的嫁接到高度大的上面,从而防止产生退化的树。
2、路径压缩
而另一个数组记录各个元素的祖先,这样就防止一步步地递归查找父亲从而损失的时间。因为并查集只要搞清楚各个元素所在的集合,
而区分不同的集合我们用的是代表元素(也就是树根),所以对于每个元素我们只需保存其祖先,从而区分不同的集合。
而我们这道题并没有使用纯正的并查集算法,而是对其进行了扩展,
我们并没有使用“1、按秩合并”(当然你可以用,那样就需要再开一个数组)
我们从“1、按秩合并”得到启示,保存“秩”的数组保存的是 元素相对于父节点的关系 ,我们岂不可以利用这种关系
(即相对于父节点的不同秩值来区分不同的集合),从而可以把两个集合合并成一个集合。
(注:此代码 relation=0 代表 和父节点同一性别)
*/
#include<stdio.h>
int father[2005];
int relation[2005];
int find_father(int i)
{
int t;
if(father[i]==i)
return i;
//计算相对于新的父节点(即根)的秩,relation[t]是老的父节点相对于新的父节点(即根)的秩,relation[i]是i元素相对于老的父节点的秩,
//类似于物理里的相对运动,得到的r[i]就是相对于新的父节点(即根)的秩。而且这个递归调用不会超过两层
t=father[i];
father[i]=find_father(father[i]);
relation[i]=(relation[i]+relation[t]+1)%2; //注意递归中把这棵树relation中的的值都更新一遍,这句的顺序 不能 和上一句 调换位置
// relation[a]的改变是伴随着father[a]的改变而更新的(有father改变就有relation改变),要是father改变了,而relation未改变,此时的relation就记录了一个错误的值,
//father未改变(即使实际的father已不是现在的值,但只要father未改变,relation的值就是“正确”的,认识到这点很重要。)
return father[i];
}
void merge(int a,int b)
{
int x,y;
x=find_father(a);
y=find_father(b);
father[x]=y;
relation[x]=(relation[b]-relation[a])%2;//relation[a]+relation[x]与relation[b]相对于新的父节点必须相差1个等级,因为他们不是gay
} //x下边的节点不用改,因为查找的时候会自动更新
int main()
{
int T,m,n,i,j,a,b,flag;
scanf("%d",&T);
for(i=1;i<=T;++i)
{
flag=0;
scanf("%d%d",&n,&m);
for(j=1;j<=n;++j) //初始化
{
father[j]=j;
relation[j]=1;
}
for(j=1;j<=m;++j)
{
scanf("%d%d",&a,&b);
if(find_father(a)==find_father(b))
{
// if(relation[a]!=(relation[b]+1)%2)
if(relation[a]==relation[b]) //说明是同性
flag=1;
}
else
merge(a,b);
}
if(flag)
printf("Scenario #%d:\\nSuspicious bugs found!\\n\\n",i);
else
printf("Scenario #%d:\\nNo suspicious bugs found!\\n\\n",i);
}
return 0;
}
3、带权并查集经典题:poj1182食物链
我说不明白==参考:http://blog.csdn.net/c0de4fun/article/details/7318642
4、并查集水题 挨着就算相交,给定某个线段,询问这一团的有多少个
HDU 1558 Segment set 并查集
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int pre[1010],sum[1010];
struct point{
double x,y;
};
struct EDGE{
point a,b;
} edge[1010];
int E;//边数
int Find(int x){
return x==pre[x]? x:pre[x]=Find(pre[x]);
}
void Merge(int a,int b){
int x=Find(a),y=Find(b);
if(x!=y){
pre[y]=x;
sum[x]+=sum[y];
}
}
double xmult(point a,point b,point c){//大于零代表a,b,c左转
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
bool OnSegment(point a,point b,point c){ //a,b,c共线时有效
return c.x>=min(a.x,b.x)&&c.x<=max(a.x,b.x)&&c.y>=min(a.y,b.y)&&c.y<=max(a.y,b.y);
}
bool Cross(point a,point b,point c,point d){//判断ab 与cd是否相交
double d1,d2,d3,d4;
d1=xmult(c,d,a);
d2=xmult(c,d,b);
d3=xmult(a,b,c);
d4=xmult(a,b,d);
if(d1*d2<0&&d3*d4<0) return 1;
else if(d1==0&&OnSegment(c,d,a)) return 1;
else if(d2==0&&OnSegment(c,d,b)) return 1;
else if(d3==0&&OnSegment(a,b,c)) return 1;
else if(d4==0&&OnSegment(a,b,d)) return 1;
return 0;
}
int main()
{
int i,j,k,T,n;
char s[10];
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);E=0;
for(i=1;i<=n;i++) pre[i]=i,sum[i]=1;
for(i=1;i<=n;i++)
{
scanf("%s",s);
if(s[0]=='P'){
E++;
scanf("%lf%lf%lf%lf",&edge[E].a.x,&edge[E].a.y,&edge[E].b.x,&edge[E].b.y);
for(j=1;j<E;j++)
if(Find(E)!=Find(j)&&Cross(edge[E].a,edge[E].b,edge[j].a,edge[j].b)) Merge(E,j);
}
else if(s[0]=='Q'){
scanf("%d",&k);
printf("%d\\n",sum[Find(k)]);
}
}
if(T) printf("\\n");
}
return 0;
}
5、并查集浇灌农田:
HDU 1198 Farm Irrigation 并查集
给出每种小单元的上下左右联通情况,求最后整个农田分几块?把每种田地的四个边转化成数组形式,相邻的相连就用并查集
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
bool type[15][4]={{1,0,0,1},{1,1,0,0},{0,0,1,1},{0,1,1,0},
{1,0,1,0},{0,1,0,1},{1,1,0,1},{1,0,1,1},{0,1,1,1},{1,1,1,0},{1,1,1,1}};
int f[300000],n,m;
char c;
int num[100][100];
void init(int n){
for(int i=1;i<=n;i++) f[i]=i;
}
int find(int x)
{
if(x==f[x]) return x;
int tmp=f[x]; f[x]=find(f[x]);
return f[x];
}
int main()
{
//freopen("cin.txt","r",stdin);
while(~scanf("%d%d",&m,&n))
{
if(m==-1&&n==-1) break;
init(n*m);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>c;
num[i][j]=c-65;
//cout<<num[i][j]<<" ";
}
// cout<<endl;
}
int count=n*m;
for(int i=1;i<=m;i++)
for(int j=1;j<n;j++)
{
if(type[num[i][j]][1]&&type[num[i][j+1]][3])
{
int fx=find(i*n-n+j),fy=find(i*n-n+j+1);
if(fx!=fy) {f[fx]=fy;count--;}
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++)
{
if(type[num[j][i]][2]&&type[num[j+1][i]][0])//就是这里 这里这里~~
{
int fx=find(j*n-n+i),fy=find(j*n+i);
if(fx!=fy) {f[fx]=fy;count--;}
}
}
cout<<count<<endl;
}
return 0;
}
6、
hdu1272小希的迷宫【并查集基础】
题意:问连接两条边是否有环,这个并查集的题用到了find函数的返回值,两个即将连接的点返回值如果相同,那么就说明要成环了!话说是不是好多图论题也可以这么搞呢?
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[100005],x,y,count,maxn,edge,point;
bool vis[100005],mark;
int find(int x)
{
if(x!=a[x]) a[x]=find(a[x]);
return a[x];
}
void addto(int x,int y)
{
x=find(x),y=find(y);
if(x==y) {
mark=0;
return ;
}
a[y]=a[x];
return ;
}
void cal()
{
for(int i=1;i<=maxn;i++)
{
if(vis[i])
{
if(a[i]==i) count++;
// point++;
if(count>1) mark=0;
}
}
}
int main()
{
// freopen("cin.txt","r",stdin);
while(~scanf("%d%d",&x,&y))
{
if(x==-1&&y==-1) break;
if(x==0&&y==0)
{
printf("Yes\\n");
continue;
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=100000;i++) a[i]=i;
mark=1;
count=0;
maxn=0;
// point=0;
addto(x,y);
vis[x]=1;
vis[y]=1;
if(maxn<x) maxn=x;
if(maxn<y) maxn=y;
while(~scanf("%d%d",&x,&y))
{
if(x==0&&y==0) break;///
addto(x,y);
vis[x]=1;
vis[y]=1;
if(maxn<x) maxn=x;
if(maxn<y) maxn=y;
}
cal();
if(mark==1) printf("Yes\\n");
else printf("No\\n");
}
return 0;
}
7、
hdu3461Code Lock【并查集+快速幂】
以上是关于并查集总结篇的主要内容,如果未能解决你的问题,请参考以下文章