所谓并查集,就是对许多集合做多次合并与查找。
其关键就是递归寻找father与进行路径压缩,基于并查集的很多题目都会在压缩上做文章。
洛谷p3367并查集模版题
#include<bits/stdc++.h>
using namespace std;
int n,m,p,fa[10010];
inline int getfather(int x)//寻找father并进行路径压缩
{
if(fa[x]==x)return x;//递归边界
fa[x]=getfather(fa[x]);//压缩
return fa[x];
}
inline void judge(int x,int y)//判断是否在一个集合
{
int fx,fy;
fx=getfather(x);
fy=getfather(y);
if(fx==fy)printf("Y\n");
else printf("N\n");
return;
}
inline void merge(int x,int y)//合并x和y所在的两个集合
{
int fx,fy;
fx=getfather(x);
fy=getfather(y);
fa[fx]=fy;
return;
}
inline void work()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int t,x,y;
scanf("%d%d%d",&t,&x,&y);
if(t==1)merge(x,y);
else judge(x,y);
}
return;
}
int main()
{
work();
return 0;
}
洛谷p1551亲戚同理
接下来,就需要一些绝妙的操作了
洛谷p1196银河英雄
需要查找战舰间的距离是最难的一点,当压缩路径时,点x需加上它father的num。
#include<bits/stdc++.h>
using namespace std;
struct ship
{
int fa,num,s;//s代表该战舰开头的队列长度,num为第几艘
}a[30010];
int n,m,p;
inline int getfather(int x)
{
if(a[x].fa==x)return x;
int pre=a[x].fa;//暂时记忆为了接下来的处理
int f=getfather(a[x].fa);
a[x].fa=f;
a[x].num+=a[pre].num;//更新x的位置
return a[x].fa=a[a[x].fa].fa;
}
inline void judge(int x,int y)
{
int fx,fy;
fx=getfather(x);
fy=getfather(y);
int ansx,ansy,ans;
ansx=a[x].num;
ansy=a[y].num;
ans=abs(ansx-ansy)-1;//计算距离
if(fx==fy)
printf("%d\n",ans);
else printf("-1\n");
return;
}
inline void merge(int x,int y)
{
int fx,fy;
fx=getfather(x);
fy=getfather(y);
a[fx].num=a[fy].s;//将y所在队列接到x所在队列之后
a[fy].s+=a[fx].s;
a[fx].fa=fy;
return;
}
inline void work()
{
scanf("%d",&p);
char temp[10];
gets(temp);
for(int i=1;i<=30000;i++)
{
a[i].fa=i;
a[i].s=1;
a[i].num=0;
}
for(int i=1;i<=p;i++)
{
int x,y;char c;
scanf("%c%d%d",&c,&x,&y);
if(c=='M')merge(x,y);
else judge(x,y);
gets(temp);//防止回车被读入
}
}
int main()
{
work();
return 0;
}
洛谷p1525关押罪犯
对于两者不能在一个集合里的关系怎么办呢,这个时候可以采用补集。
若x与y敌对,则另x与(y+n)为同一集合。
#include<bits/stdc++.h>
using namespace std;
struct criminal
{
int x,y,w;
}c[100010];
int fa[40010],n,m,num=0;
inline int getfather(int x)
{
if(fa[x]==x)return x;
fa[x]=getfather(fa[x]);
return fa[x];
}
inline bool mycmp(criminal a,criminal b)
{
return(a.w>b.w);
}
inline void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].w);
sort(c+1,c+m+1,mycmp);//为了矛盾尽可能小
return;
}
inline void work()
{
for(int i=1;i<=2*n;i++)fa[i]=i;//两倍!
for(int i=1;i<=m;i++)
{
int fx=getfather(c[i].x);
int fy=getfather(c[i].y);
if(fx==fy)//x与y同一集合说明他们都与某人敌对,无法再分开
{
printf("%d",c[i].w);
exit(0);
}
fa[fx]=getfather(c[i].y+n);//敌对,getfather是为了让补集进行压缩
fa[fy]=getfather(c[i].x+n);//double敌对
}
printf("0");
return;
}
int main()
{
init();
work();
return 0;
}
洛谷p2024食物链
本来想用两个补集,后来发现,emmm……
然后就采用了加权并查集。
对于结点x,fa[x][0]存储它的father,fa[x][1]存储它和father间的关系。
0表示为同一类,1表示
#include<bits/stdc++.h>
using namespace std;
int fa[60010][2],n,ans,k;
inline void init()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)fa[i][0]=i,fa[i][1]=0;
return;
}
inline int getfather(int x)
{
if(fa[x][0]==x)return x;
int f=fa[x][0];
fa[x][0]=getfather(fa[x][0]);
fa[x][1]=(fa[x][1]+fa[f][1])%3;
return fa[x][0];
}
inline void unio(int x,int y,int t)
{
if(t==1)
{
int fx=getfather(x);
int fy=getfather(y);
if(fx==fy&&fa[x][1]!=fa[y][1])
{
ans++;
return;
}
else
{
fa[fx][0]=fy;
fa[fx][1]=(fa[y][1]-fa[x][1]+3)%3;
}
}
else
{
int fx=getfather(x);
int fy=getfather(y);
if(fx==fy&&(fa[x][1]!=(fa[y][1]+1)%3))
{
ans++;
return;
}
else
{
fa[fx][0]=fy;
fa[fx][1]=(fa[y][1]-fa[x][1]+4)%3;
}
}
return;
}
inline void work()
{
for(int i=1;i<=k;i++)
{
int x,y,t;
scanf("%d%d%d",&t,&x,&y);
if(x>n||y>n||(t==2&&x==y))
{
ans++;
continue;
}
unio(x,y,t);
}
printf("%d",ans);
return;
}
int main()
{
init();
work();
return 0;
}