并查集和带权并查集

        第一次听老师讲并查集还以为是很复杂的数据结构,实操之后发现用数组就可以模拟。

先是并查集的模板题。

P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 题解 P3367 【【模板】并查集】 - 加载错误 的博客 - 洛谷博客 (luogu.org)

这个大佬讲的很清楚。

#include 
using namespace std;
const int MAX=1e4+10;
int f[MAX];
int parity[MAX]; 
void init(int n){
	for(int i=0;i<=n;i++){
		f[i]=i;
	}//初始化掌门。掌门是他自己
}

int find_f(int x){
	if(x!=f[x])//取值,压缩,更新 
	{
		int t=f[x];
		f[x]=find_f(x);
		parity[x]^=parity[t];
	}
	 //把这个子节点的老大换成老大的老大 
	return f[x]; 
}//压缩路径 

void join(int x,int y){
	int fx=find_f(x);
	int fy=find_f(y);
	if(fx!=fy)
	{
		f[fy]=fx;
	} 
}
 
int main(){
	
	return 0;
}

简述一下并查集的几个部分:

1.初始化函数:init

每个人的掌门,也就是一开始所有人都没有联通的情况下,他们的掌门就是他们自己。用f[x]表示

2.寻根函数:find

寻找每个人的掌门,这个函数涉及到了路径压缩,即在查询掌门的时候,就把f数组的内容改成掌门,这样接下来的查询只需要一次就可以完成。

3.合并函数:join

寻找两个人的掌门,如果掌门不一样就选取一个掌门为根,合并起来。注意join之后每个f[x]存的内容都是自己的掌门,这得益于路径压缩。

并查集的应用:城市联通线路问题.

Problem - 1232 (hdu.edu.cn) HDU-1232

#include 
#include 
using namespace std;
const int MAX=1e3+10;
int f[MAX];
int count1[MAX];
void init(int n){
	for(int i=1;i<=n;i++){
		f[i]=i;
	}//初始化掌门。掌门是他自己
}

int find_f(int x){
	if(x!=f[x]) return f[x]=find_f(f[x]);//把这个子节点的老大换成老大的老大 
	return f[x]; 
}//压缩路径 

void join(int x,int y){
	int fx=find_f(x);
	int fy=find_f(y);
	if(fx!=fy) f[fy]=fx;
}//合并子集
 
int main(){
	//首先用一个数组保存掌门 
	int n,t;
	while(cin>>n&&n)
	{
		cin>>t;
		init(n);
		for(int i=0;i>a>>b;
			join(a,b);
		}
        int ans=0;
		for(int i=1;i<=n;i++)
        {
            if(f[i]==i)
                ans++;
        }
		cout<

这个时候我踩了一个坑,问题在于我没有意识到join之后由于路径压缩fx指向的就是自己的掌门,在最后一次计算ans的时候用的是find函数遍历。导致时间增加。

最后有一个点,memset的赋值是赋给字节,所以最后一个参量最好是sizeof(arr)

总结一下,并查集给我的感觉就是适用于一个平面中有很多分散的点,然后给出了几个点的连接关系。最后查询这些点之间是否有联系。其实掌门就是随机选择了一个联通内的点,并没有什么特殊性,只不过方便寻找而已.

带权并查集:

P2024 [NOI2001] 食物链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

带权并查集就是在点的链接关系之间多了一个权,请看题目。

#include 
using namespace std;
const int MAX=5e4+10;
int f[MAX];
int chain[MAX]; //食物链关系,1是吃,0同类,2被吃 
void init(int n){
	for(int i=0;i<=n;i++){
		f[i]=i;
	}//初始化掌门。掌门是他自己
}

int find_f(int x){
	if(x!=f[x])
	{
		int t=f[x];
		f[x]=find_f(f[x]);
		chain[x]=(chain[x]+chain[t])%3;
	}
	
	return f[x]; 
}

void join(int x,int y){
	int fx=find_f(x);
	int fy=find_f(y);
	if(fx!=fy) f[fy]=fx;
}//合并子集
 
int main(){
	int n,t;
	cin>>n>>t;
	init(n);
	int count=0;
	int pre=0;
	for(int i=0;in||b>n)
		{
			count++;
		}else if(a==b&&v==2)
		{
			count++;
		}else
		{
			int fx=find_f(a);
			int fy=find_f(b);
			
			//这里可以缩到join里面 
			if(fx==fy)
			{
				if(v!=(chain[a]-chain[b]+3)%3)
				count++;
			}else
			{
				f[fx]=fy;//相当于把a接到了b上。 
				chain[fx]=(-chain[a]+v+chain[b])%3;
			}
		}
	}
	cout<

以下是做题的思路。

        在他说的话没有明显错误时判断如下:

       一开始我们并不知道两个动物之间有什么关系,也不知道他们是什么物种,但是当两个动物之间没有关系的时候(即两个物种所在的点不连通的时候),我们可以知道他说的话是真的。
       根据他的话,不妨就把其中一个当成根节点,把value直接赋值给它。比如说a吃b那么a到b的关系可以直接赋值chainb为1,如果b吃c,chainc赋值为1,这样abc这条链上可以直接通过chain访问到a和c的关系。这样我们就给边附上了权
         同理经过多次赋值之后,会出现两个生物都有了自己的根节点,就意味着有了自己的生态位,也说明了他们是联通的,可以通过边权来溯源寻找生物之间的关系。
         因为这道题所有生物一定是abc的一种,所以生物间一定是联通的,只不过没有合并罢了,所以fx!=fy的时候说明现在他们没有形成判断体系,要根据他说的话合并。如果形成判断体系,然而他说的chain和实际的chain对不上,说话就是错误的。

        代码方面:

需要修改的地方是,加上一个数组存储边权,(虽然说是边,但是一般存储在某个点上来模拟)

寻根函数,每寻找一次根,都要改变它的值,即a吃b,b吃c,那么c的掌门可以归结到a上,fx存储的是a的下标,b和c的关系也需要改变到a和c的关系,这样两条边的权就变成了一条边的权,这个权如何递推是带权并查集需要思考的一个难点。注意这个时候也是有路径压缩的。

然后是join函数,需要添加两个掌门,权如何合并的问题,即两个不同的掌门如何合并到一起,它的边权如何更新? 只需要根据题里给的两个掌门之间的权,然后链接边即可。

还有一道带权并查集的题,很可惜由于我没学离散化,暂时码不出来,但是可以用带权并查集来做

P5937 [CEOI1999] Parity Game - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

总结一下带权并查集:在一个平面内的点的连通性需要考察,点的关系也需要考察。

通过维护点之间连线的权,不仅可以知道他们是有关系的,还可以通过权来指定访问这个关系到底是什么。

需要注意的是点如何连接,掌门如何合并。这个过程中权的关系是怎么递推的

常见的询问方法就是,判断这个人说了几句假话,一般来说,前几句是条件(即给出这个图的状态)。后几句是查询(点与点的关系是不是跟图一样?)通过并查集很容易就能做出来。

感谢并查集让我a了第一道蓝题

你可能感兴趣的:(TJUACM寒假集训,算法)