并查集(思路加简单例题)

并查集(思路加简单例题)

并查集,顾名思义就是 合并集合,查找集合这两个功能。

并查集一般是作用于连通性问题(可能是我太菜了)

首先我们看看并查集的查找,

举个例子

小绿是小红老大,小红是小蓝老大,小蓝是小明老大。

那么小绿,小红,小蓝,小明,这四个人就因为谁是谁老大这个关系而连通

查找函数find()查找与你连通的最上面一位//谁是你最大的老大

f[]数组,用来储存与你连通的上一位//谁是你的老大

比如 f[小明]=小蓝,f[小蓝]=小红,f[小红]=小绿

find[小明]=小绿,find[小蓝]=小绿,find[小红]=小绿;//归根结底这些人的老大都是小绿

int f[maxn];
int find(int x)
{
 if(x==f[x])
 return x;
 else
 find(f[x]);
}//find函数


接下来,来看看合并函数join()

今天的风儿甚是喧嚣啊,果不其然,今天新来了伙人

这伙人里小a是小b的老大,小b是小c的老大。

扫描二维码关注公众号,回复: 12090378 查看本文章

那要想将这两伙人合在一起,该怎么办呢?
简单,只需要让他们认同一个人当老大就行了,(老大从小a和小绿里面随便选一个都可以)

比如让小a认小绿当老大。那么f[小a]=小绿,那么find(小b)=小绿;

这时候就需要用到join()函数了

join函数就是将两个集合合并,就是让其中一个集合最大的老大认另外一个集合最大的老大作老大

void join(int x,int y)
{
    int a=find(x);
    int b=find(y);
    if(a!=b)
    f[a]=b;
}//这种有时会超内存,但不容易出错

void join(int x,int y)
{
f[find(x)]=find(y);
}//这种不容易超内存,但有时候会出问题

一、模板题

https://www.acwing.com/problem/content/838/

一共有n个数,编号是1~n,最开始每个数各自在一个集合中。

现在要进行m个操作,操作共有两种:

  1. “M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. “Q a b”,询问编号为a和b的两个数是否在同一个集合中;

输入格式

第一行输入整数n和m。

接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。

输出格式

对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。

每个结果占一行。

数据范围

1≤n,m 1 0 5 10^5 105

#include<bits/stdc++.h>
using namespace std;
int f[100006];
// 该函数的含义:查找a所在集合的祖先节点下标,从1开始, 并内部更新f[a]为a节点的祖先节点。
int find(int a)
{
    // 根据通项公式,假设f[a]的祖先节点已知。
    if (f[a] != a) f[a] = find(f[a]);
    return f[a];
}
void join(int b,int c)
{
	int x=find(b);
	int y=find(c);
	if(x!=y)
	f[x]=y;
}

int main()
{
    int n, m;
    scanf("%d %d", &n, &m);

    // 初始化每个集合
    for (int i = 1; i <= n; i++) f[i] = i;

    int a, b;
    char op[2];
    while (m--)
    {
        scanf("%s%d%d", op, &a, &b);
        if (op[0] == 'M') join(a,b);
        else {
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

二、提高题

https://www.acwing.com/problem/content/242/

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。

A吃B, B吃C,C吃A。

现有N个动物,以1-N编号。

每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这N个动物所构成的食物链关系进行描述:

第一种说法是”1 X Y”,表示X和Y是同类。

第二种说法是”2 X Y”,表示X吃Y。

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。

你的任务是根据给定的N和K句话,输出假话的总数。

输入格式

第一行是两个整数N和K,以一个空格分隔。

以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。

若D=1,则表示X和Y是同类。

若D=2,则表示X吃Y。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤50000
0≤K≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

题解

1.并查集(拓展域)//一般有多种关系的,都可以用拓展域。
这道题目我们主要是要开三个拓展的域,也就是同类域(1,n),捕食域(n+1,2n),以及天敌域(2n+1,3n).

即f[x]表示同类的关系,f[x+n]表示捕食关系,f[x+2n]表示天敌关系

如果x,y是同类,但是x的捕食域有y,那么错误
如果x,y是同类,但是x的天敌域有y,那么错误
如果x是y的天敌,但是x的同类域中有y,那么错误
如果x是y的天敌,但是x的天敌域中有y,那么错误

2.带权并查集(下面有进行介绍)

在这个题中相对关系就是食物链上的关系,因此带权并查集中的权值就应该记录两个动物在食物链上的相对关系,A->B为0表示同类,为1表示A吃B,为2表示A被B吃。这个值不同于前面两个题中的区间合、分数差,它是不可以直接累加的,要考虑三个问题:

1.路径压缩时,如何更新Value

如果现在有A->B为1,B->C为1,怎么求A->C?显然A吃B,B吃C,那么由题意C应该吃A,那么A->C应该为2;

如果现在有A->B为2,B->C为2,怎么求A->C?显然B吃A,C吃B,那么由题意A应该吃C,那么A->C应该为1;

如果现在有A->B为0,B->C为1,怎么求A->C?显然A、B同类,B吃C,那么由题意A应该吃C,那么A->C应该为1;

找规律不难发现,A->C = (A->B + B->C) % 3,因此关系值的更新需要累加再模3。

2.区间合并时,如何更新Value

由1不难发现,本题的Value更新无非就是多了个取模操作,因此不难验证区间合并的更新操作应该为:

value[px] = (-value[x] + value[y] + s)%3;//s表示x和y之间的关系

拓展域代码

#include<bits/stdc++.h>
using namespace std;
int f[200005];		
int d,x,y;
int n,k,sum=0;
int find(int a)
{
	if(a==f[a])
	return a;
	else
	find(f[a]);
}
void join(int a,int b)
{
	f[find(a)]=find(b);
}
int main()
{

	cin>>n>>k;
	for(int i=1;i<=3*n;i++)
	f[i]=i;
	while (k--)
	{
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n)
		sum++;
		else
		{
			if(d==1)
			{
			   if(find(x)==find(y+n)||find(x)==find(y+n+n))//如果x是在y的捕食域或者y的天敌域内就说明是错误的 
			   sum++;
			   else
			   {
			   	join(x,y);//表示x和y是同类 
			   	join(x+n,y+n);//x和y是同类,那么x和y的捕食域一样 
			   	join(x+n+n,y+n+n);//x和y的天敌域一样 
			   }
			}
			else
			{
				if(x==y||find(x)==find(y+n)||find(x)==find(y))//表示x等于y,x在y的捕食域,x和y是同类,三种情况 
				sum++;
				else
				{
					join(x+n,y);//y在x的捕食域 
					join(x,y+n+n);//x在y的天敌域 
					join(x+n+n,y+n);//x的天敌是在y的捕食域内 
				}
				
			}
	
		}
	}	
		cout<<sum<<endl;
	return 0;
}

三、带权并查集(合并和查找都得更新权值)

每一条边都记录了每个节点到根节点的一个权值,这个权值该设为什么由具体的问题而定,一般都是两个节点之间的某一种相对的关系,但是考虑到权值就会有两个问题:

1.每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值

2.在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同。

下面就来看在这两个过程中,如何更新权值:


int find(int x)
{
	if (x != f[x])
	{
		int t = f[x];
		f[x] = find(f[x]);//路径压缩
		value[x] += value[t];
	}
	return parent[x];
}

可以看到更新权值只多了两行代码,先记录下原本父节点的编号,因为在路径压缩后父节点就变为根节点了,再将当前节点的权值加上原本父节点的权值,此时父节点的权值已经是父节点到根节点的权值了,因此加上这个权值就会得到当前节点到根节点的权值。

合并:


		int px = find(x);
		int py = find(y);
		if (px != py)
		{
			f[px] = py;
			value[px] = -value[x] + value[y] + s;//s表示x和y之间的关系
		}

在这里插入图片描述

可以将他看成向量的加减。

HihoCoder-1515-分数调查

描述

小Hi的学校总共有N名学生,编号1-N。学校刚刚进行了一场全校的古诗文水平测验。

学校没有公布测验的成绩,所以小Hi只能得到一些小道消息,例如X号同学的分数比Y号同学的分数高S分。

小Hi想知道利用这些消息,能不能判断出某两位同学之间的分数高低?

输入

第一行包含三个整数N, M和Q。N表示学生总数,M表示小Hi知道消息的总数,Q表示小Hi想询问的数量。

以下M行每行三个整数,X, Y和S。表示X号同学的分数比Y号同学的分数高S分。

以下Q行每行两个整数,X和Y。表示小Hi想知道X号同学的分数比Y号同学的分数高几分。

对于50%的数据,1 <= N, M, Q <= 1000

对于100%的数据,1 <= N, M, Q<= 100000 1 <= X, Y <= N -1000 <= S <= 1000

数据保证没有矛盾。

输出

对于每个询问,如果不能判断出X比Y高几分输出-1。否则输出X比Y高的分数。

样例输入

10 5 3  
1 2 10  
2 3 10  
4 5 -10  
5 6 -10  
2 5 10  
1 10  
1 5  
3 5 

样例输出

-1  
20  
0
#include<bits/stdc++.h>
using namespace std; 
const int maxN = 100005;
int f[maxN];
int score[maxN];
int find(int x)
{
	if (x != f[x])
	{
		int t = f[x];
		f[x] = find(f[x]);
		score[x] += score[t];
	}
	return f[x];
}
int main()
{
	int n, m, q;
	scanf("%d%d%d", &n, &m, &q);
	for (int i = 1 ; i <= n ; i ++ )
	{
		f[i] = i;
	}
	while ( m-- )
	{
		int x, y, s;
		scanf("%d%d%d", &x, &y, &s);
		int px = find(x);
		int py = find(y);
		if (px != py)//合并 
		{
			f[px] = py;
			score[px] = -score[x] + score[y] + s;
		}
	}
	while (q --)
	{
		int x, y;
		scanf("%d%d",&x,&y);
		if (find(x) != find(y))
		{
			printf("-1\n");
		}
		else {
			printf("%d\n", score[x] - score[y]);
		}
	}	
	return 0;
}

 (int i = 1 ; i <= n ; i ++ )
	{
		f[i] = i;
	}
	while ( m-- )
	{
		int x, y, s;
		scanf("%d%d%d", &x, &y, &s);
		int px = find(x);
		int py = find(y);
		if (px != py)//合并 
		{
			f[px] = py;
			score[px] = -score[x] + score[y] + s;
		}
	}
	while (q --)
	{
		int x, y;
		scanf("%d%d",&x,&y);
		if (find(x) != find(y))
		{
			printf("-1\n");
		}
		else {
			printf("%d\n", score[x] - score[y]);
		}
	}	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/hhuhgfhggy/article/details/109412999