最小生成树,连接格点思维过程与题解

目录

思路:

思维过程:

一、如何走出第一步——二维转一维

二、如何使用克鲁斯卡尔(Kruskal)算法解这道题——枚举

三、如何连线——制定枚举方案以及化繁为简、分两步连线

1.制定枚举方案。

2.化繁为简、分两步连线。

3.注意范围。

总代码: 


思路:

这道题要我们联通一个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;
}

这题很棘手,但要自己完成。可以看思路,但最好不要抄题解。 

  • 44
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值