目录
二、如何使用克鲁斯卡尔(Kruskal)算法解这道题——枚举
思路:
这道题要我们联通一个M*N的矩阵中的所有点,并给出了已经联通的点,还要我们输出最小的值,一看就知道要用并查集和克鲁斯卡尔(Kruskal)算法解题。
思维过程:
一、如何走出第一步——二维转一维
这道题麻烦的是,矩阵是个二维的东西,而并查集的中心思想find()函数是一维查找。于是,只有两种方法可以让我们继续解题:
1.二维转一维,给每个格点一个编号(下标)。我的代码:
int ci(int x, int y) //转化为下标(二维转一维)
{
return (x-1)*n+y;
}
2.升级find()函数,让find()函数可以处理二维的查找。这个我就不讲了,我重点讲上面一个。
二、如何使用克鲁斯卡尔(Kruskal)算法解这道题——枚举
做好准备工作后,就可以开始解题了。可是克鲁斯卡尔(Kruskal)算法要给所有边先排序(不清楚的可以看我的另一篇博文最小生成数算法(图论)-CSDN博客,或看其他有关知识),而这题的矩阵不好排序。但其实不用排序,因为这道已经告诉你如何收费、如何连接了,只需要联通所有点即可。你想,从上面连接和从下面连接都连接上了,没区别,为什么不从上面连接呢?从上面连接省事,还不容易出错。所以,直接从上到下、从左到右地枚举就行了。
三、如何连线——制定枚举方案以及化繁为简、分两步连线
1.制定枚举方案。
但是,还没完,如何连线又成了问题。相邻的点都可以连线(上下连线、左右连线),可我们是用一维的数组存储的,不支持在二维的矩阵中连线。但是,我们可以注意到,我前面写的是“所以,直接从上到下、从左到右地枚举就行了”。很明显,我是在一个矩阵中枚举的。那如何在矩阵中枚举呢?双重for循环!在双层的for循环中枚举,调用ci()函数,再进行判断和合并(连线)就可以了。
2.化繁为简、分两步连线。
但是方向是四个方向(上下左右),那是不是要判断很多次呢?不!上下可以归为下,左右可以归为左,用两个单层for循环来执行,这样可以简便许多。为什么能这样做?你想,左右和右左不是一样的吗?上下和下上也是一样的呀!为什么要把一样的东西判断两遍呢?所以,这样做既不会重复,也不会遗漏。
3.注意范围。
从上到下枚举时,要少一位,不然会出数组边界。从左到右枚举时,也要少一位,不然也会出边界。
连线代码如下:
long long ans = 0;
for(int i = 1; i <= m-1; i++) //从上到下,注意范围
{
for(int j = 1; j <= n; j++) //从左到右
{
int ft = find(ci(i, j));
int fd = find(ci(i+1, j)); //下面的
if(ft != fd)
{
ans += 1;
fa[fd] = ft; //连线
}
}
}
for(int i = 1; i <= m; i++) //从上到下
{
for(int j = 1; j <= n-1; j++) //从左到右,注意范围
{
int ft = find(ci(i, j));
int fr = find(ci(i, j+1)); //右边的
if(ft != fr)
{
ans += 2;
fa[fr] = ft; //连线
}
}
}
总代码:
#include<bits/stdc++.h>
using namespace std;
int fa[1000001];
int m, n;
int ci(int x, int y) //转化为下标
{
return (x-1)*n+y;
}
int find(int i) //查找函数
{
if(fa[i] != i)
fa[i] = find(fa[i]);
return fa[i];
}
int main()
{
scanf("%d %d", &m, &n);
for(int i = 1; i <= m; i++) //初始化
{
for(int j = 1; j <= n; j++)
fa[ci(i, j)] = ci(i, j);
}
int x1, y1, x2, y2;
while(cin>>x1>>y1>>x2>>y2) //已联的线
{
int f1 = find(ci(x1, y1));
int f2 = find(ci(x2, y2));
fa[f1] = f2;
}
long long ans = 0;
for(int i = 1; i <= m-1; i++) //注意范围
{
for(int j = 1; j <= n; j++)
{
int ft = find(ci(i, j));
int fd = find(ci(i+1, j));
if(ft != fd)
{
ans += 1;
fa[fd] = ft;
}
}
}
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n-1; j++) //注意范围
{
int ft = find(ci(i, j));
int fr = find(ci(i, j+1));
if(ft != fr)
{
ans += 2;
fa[fr] = ft;
}
}
}
printf("%d", ans);
return 0;
}
这题很棘手,但要自己完成。可以看思路,但最好不要抄题解。