学习目标:
每天睡前是否感到浑浑噩噩,一天又在不知不觉中过去,回想我今天都干了什么呢?
啊~我这一天又什么也没干,好有罪恶感啊,不行,我明天一定要好好学算法(手动狗头)。
明日复明日,明日何其多?不要等明天啦,和小编一起,每天睡前一道算法题,不仅解决你一天的空虚,更能助你安心入眠,远离熬夜。还能学到一点算法知识。不要小看这些知识哦,不积跬步无以至千里,不积小流无以成江海。每位大佬都不是一夜成名,都是从小白做起,日积月累,终成大佬,和小编一起,每日一题,走向大佬之路吧!
学习内容:
我们的题基本都是一些较为基础,适合日(睡)常(前)看的题,不会让你一眼就看出答案,是稍稍一点脚就能摸到头绪,但是在想的过程中又一时不知如何实现,再一想,又柳暗花明,最终跟着小编的思路一起解决问题,有不同思路可以发在评论区,一起讨论。“你若在,我必回”。
我们今天的内容是有关高级数据结构——并查集。小编会对并查集进行一个基本的讲解并给出通用模板,然后用一道例题来加深理解,毕竟实用才是我们的第一要义!话不多说,先来简单介绍下并查集:
1.并查集是一种树形数据结构,用于处理一些不相交集合的合并及查询问题
2.并查集包含两种操作:查找与合并
查找用于查询两个元素是否在同一个集合中;合并是把两个不相交的集合合并为一个集合。
得知了什么是并查集以后,小编直接给出并查集的模板,告诉大家怎么用就行,毕竟实用、好用才是王道!
1.预准备
我们的并查集主要是通过数组来完成的,所以我们要现定义一个数组pre,并对他进行初始化,初始化的意义是每个点i的父节点是pre[i],开始的时候每个节点都是自己的父节点,也就是各个点相互独立。
int pre[N];
void init(int len){
for(int i=0;i<=len;i++){
pre[i]=i;
}
}
2.查找
当我们需要查找某个节点的祖宗(最终父节点),也就是树的根,它的父节点就是它自己。
可能有小伙伴有这样的疑问,不是每个节点的父节点都是自己吗?直接调用pre[i]不就行吗?
A:这是因为我们虽然初始化的时候是每个节点相互独立,但不代表就永远独立,可能后续会发生集合的合并,使它的父节点改变。因此我们需要查找它的父节点,这也是我们合并的基础。
我们查找的基本模型是:
int find(int x){
while(pre[x]!=x){
x=pre[x];
}
return x;
}
这个原理很简单,就是逐级向上寻找,但是每次都逐级寻找会浪费大量时间,我们有没有一种方法能直接找到呢?答案是肯定的。我们想一下,并查集是一种树形结构,花费时间的原因是要从底层逐级上报,就像古代县令,如果想找到皇上,是不是要经过逐级审批,最终才能见到,那么最好的方式是什么?县令与皇上直接联系,让县令直接归皇上管,跳过中间的大臣。这种方法虽然在古代的制度中是不可行的,但在我们的代码里是完全可以的!我们在查找的同时,直接让每一级的节点,它的父节点都变成祖宗。看下代码怎么写:
int find(int x) {
return pre[x]=pre[x]==x?x:find(pre[x]);
}
是不是更加简洁,这种方法我们称作压缩路径法。这也是小编推荐大家使用的方法。比第一个效率更高。
3.合并
这就是我们前面说的使一个节点的祖宗变成另一个节点祖宗的子节点
void join(int x,int y){
pre[find(x)]=find(y);
}
以上就是我们并查集的基本模板,接下来看下我们今天的题目:
有n个彩灯每个彩灯有一种颜色coli(彩灯从1到n标号),有m根细线将它们连接在一起。你想用这些彩灯作出一朵美丽的花,进一步说你想从这m根细线中保留n-1根,且需要保证所有彩灯是连通在一起的。你希望这朵花五彩斑斓,因此你想要连接同颜色彩灯的细线越少越好。请问这个最小值是多少?
数据范围:
2 ≤n≤100,1≤m≤200,1≤coli≤n
Input
第一行两个整数n, m表示彩灯数与细线数。 接下来一行n个数,表示n个彩灯的颜色。 接下来m行,每行两个数u, v,表示u, v这两个彩灯之间有一条细线连接。
Output
输出仅一行,一个整数表示最少多少根细线连接同色灯
Sample
Inputcopy Outputcopy 4 4 1 1 2 2 1 2 2 3 3 4 4 1 1
乍一看我们还无从下手,甚至是想不到到这是并查集的题目,但实际我们大部分的并查集题目,都不是简单能看出的。下面就和小编一起来捋一下思路吧。
题目大概是说有n个灯笼,第i个的颜色是coli,我们不妨定义一个数组a[N]表示,然后希望所有灯是连通在一起的,有m条细线但是是特定的两个灯笼相连,我们从中挑选出n-1条线,使得所有灯是连通的并且连接相同颜色灯笼的细线最少。
了解题意之后,我们和并查集联想一下,是不是m条线就可以看作归并操作,直到n个灯笼连通,然后我们可以用0和1表示两个灯笼是否同色,同色为1,不同为0。这样我们可以定义一个结构体,包含三个变量u、v、w,w表示的是绳子为1还是0。
struct edg{
int u,v,w;
};
edg b[N];
for(i=1;i<=m;i++){
scanf("%d%d",&b[i].u,&b[i].v);
b[i].w=(a[b[i].u] == a[b[i].v]);
}
然后我们将b按照w由小到大排序,然后开始计算最少需要多少相同的绳子。最终代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=50000;
int pre[N],a[N],n,m;
struct edg{
int u,v,w;
};
edg b[N];
int find(int x) {
return pre[x]==x?x:find(pre[x]);
}
bool cmp(edg x,edg y){
return x.w<y.w;
}
int main() {
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",a+i);
}
for(i=1;i<=n;i++){
pre[i]=i;
}
for(i=1;i<=m;i++){
scanf("%d%d",&b[i].u,&b[i].v);
b[i].w=(a[b[i].u] == a[b[i].v]);
}
sort(b+1,b+m+1,cmp);
int ans=0;
for(i=1;i<=m;i++){
int x=b[i].u,y=b[i].v;
int fx=find(x),fy=find(y);
if(fx == fy) continue;
pre[fx]=fy;
ans+=b[i].w;
}
printf("%d",ans);
return 0;
}
以上就是我们今天的题目,稍有难度。纸上来得终觉浅,大家可以看完再自己写一遍,可以仿照小编的思路,点击下方链接也来练一下吧!众里寻花https://vjudge.net/contest/480430#problem/G
每天坚持是一件很累的事,即使很慢,但也不要停止。
大家记得点赞收藏,防止找不到哦。
欢迎大家订阅小编的每日一题专栏,会每天更新,都是用心准备的哦!
若有不同思路,欢迎评论区留言,看到必回,Goodnight!