并查集总结

并查集模板
void init()//初始化
{
 for (int i = 1; i <= n; i++)
  f[i] = i;
}
int find(int x)//路径压缩,寻根
{
 return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y)//合并两个元素所在集合
{
 int t1 = find(x), t2 = find(y);
 if (t1 != t2) f[t2] = t1;
}
int same(int x, int y)//判断两元素是否在一个集合内
{
 return find(x) == find(y);
}
并查集应用
1:求连通分量的个数
2:求某元素所在集合的元素个数
3:求每个连通分量中元素的个数
4:判断元素是否在同一集合
5:种类并查集
6:判断图中是否有环
7.带权并查集
并查集应用
1:求连通分量的个数
for (int i = 0; i < m; i++)//合并输入关系
{
 scanf("%d%d", &u, &v);
 merge(u, v);
}
int sum = 0;//连通分量的个数
for (int i = 1; i <= n; i++)
 if (f[i] == i) sum++;
此外,如果sum>1,则可以得到图不连通
2:求某元素所在集合的元素个数
void init()
{
 for (int i = 0; i < n; i++)
 {
  f[i] = i;
  num[i] = 1;//初始化每个元素所在集合中总元素的个数
 }
}
int find(int x)
{
 return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y)
{
 int t1 = find(x), t2 = find(y);
 if (t1 != t2)
 {
  f[t2] = t1;
  num[t1] += num[t2];//合并集合同时合并集合的个数
 }
}
num数组用于记录当前元素所在集合元素个数。num[find(i)]即为所求
3:求每个连通分量中元素的个数
先用num数组来求出各个连通分量的元素个数;然后找出根节点。
for (int i = 1; i <= n; i++)
{
 if (f[i] == i)//集合的祖先代表元素
  ans[cnt++] = num[i];
}
4:判断元素是否在同一集合
直接用same()函数来判断
5:种类并查集
以经典的食物链为例:
由于N和K很大,故需高效的维护动物之间的关系,对于每只动物i创建3个元素i-A,i-B,i-C,
并用这3*N个元素建立并查集,这个并查集维护如下信息:
1.i-x表示“i属于种类x”
2.并查集里到达每一个组表示组内所有元素代表的情况都同时发生或者不发生
例如,如果i-A和j-B在同一个组里,就表示如果i属于种类A那么j一定属于种类B,反之亦然。
因此,对于每一条信息,只需按照如下操作即可:
1.x和y同类———合并x-A和y-A,x-B和y-B,x-C和y-C
2.x吃y—————合并x-A和y-B,x-B和y-C,x-C和y-A
注意:在合并之前,需要先判断合并是否会产生矛盾
由于从始至终只知道相对关系,同时维护了三种可能,所以判断矛盾的时候任选一种判断就可以了。
下边是实现代码
#include <cstdio>
#define N 50005
int f[3 * N];
int n, k;
void init()
{
 for (int i = 1; i <= 3 * n; i++)
  f[i] = i;
}
int find(int x)
{
 return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y)
{
 int t1 = find(x), t2 = find(y);
 if (t1 != t2) f[t2] = t1;//以左为尊
}
int same(int x, int y)
{
 return find(x) == find(y);
}
int main()
{
 int ans = 0, type, a, b;
 scanf("%d%d", &n, &k);
 init();
 while (k--)
 {
  scanf("%d%d%d", &type, &a, &b);
  if (a > n || b > n) { ans++; continue; }
  if (type == 1)
  {
   if (same(a, b + n) || same(a + n, b))//若想成为同类,就不可能有a吃b或b吃a的关系
    ans++;
   else//a,b是同类,需维护3种情况
   {
    merge(a, b);//a,b都是A
    merge(a + n, b + n);//a,b都是B
    merge(a + 2 * n, b + 2 * n);//a,b都是C
   }
  }
  else
  {
   if (same(a, b) || same(a + n, b))//若想a吃b,则a,b不可能是同类,也不可能b吃a
    ans++;
   else//表示a吃b,需要维护这三种可能
   {
    merge(a, b + n);//a是A,b是B
    merge(a + n, b + 2 * n);//a是B,b是C
    merge(a + 2 * n, b);//a是C,b是A
   }
  }
 }
 printf("%d\n", ans);
 return 0;
}
6:判断图中是否有环
合并两个元素前,判断是否已经在同一集合(用same函数),若在,则已经形成了环
7.带权并查集
/*
带权并查集的应用
题意: 说是有n块砖,编号从1到n,有两种操作,第一是把含有x编号的那一堆砖放到含有编号y的那一堆砖的上面
,第二是查询编号为x的砖的下面有多少块砖。
思路:带权并查集,用dis[i]表示元素i下边有多少个元素,num[i]表示元素i所在堆的砖块总数
或者说
dis[i]:当前元素到树根的距离
num[i]:当前元素所在树的大小(即树中元素个数)
#include <cstdio>
#define N 30005
using namespace std;
int dis[N], f[N], num[N];
void init()
{
 for (int i = 1; i <= N; i++)
 {
  f[i] = i;
  dis[i] = 0;//当前点到树根的距离
  num[i] = 1;//每棵树初始大小
 }
}
int find(int x)
{
 if (x == f[x])
  return x;
 else
 {
  int tmp = f[x];
  f[x] = find(tmp);
  dis[x] += dis[tmp];
  //压缩路径后,当前元素的cnt值==他原来父亲(即路径压缩之前的父亲)的cnt值+当前元素相对于原来父亲的cnt值
  //也就是说,当它的父亲完成路径压缩时,它的cnt值将是它与原来父亲的相对值加上现在父亲与“顶头上司”的相对值
  return f[x];
 }
}
void merge(int x, int y)
{
 int t1 = find(x), t2 = find(y);
 if (t1 != t2)
 {
  f[t1] = t2;
  dis[t1] = num[t2];//更新当前元素到树根的距离。
  num[t2] += num[t1];//更新树的大小
 }
}
int main()
{
 int T;
 while (scanf("%d", &T) != EOF)
 {
  init();
  while (T--)
  {
   getchar();
   int u, v;
   char ch;
   scanf("%c", &ch);
   if (ch == 'M')
   {
    scanf("%d%d", &u, &v);
    merge(u, v);
   }
   else
   {
    scanf("%d", &u);
    find(u);//每次查询时,再次路径压缩,更新dis
    printf("%d\n", dis[u]);
   }
  }
 }
 return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值