连接的管道 (克鲁斯卡尔算法)



连接的管道

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Problem Description
老 Jack 有一片农田,以往几年都是靠天吃饭的。但是今年老天格外的不开眼,大旱。所以老 Jack 决定用管道将他的所有相邻的农田全部都串联起来,这样他就可以从远处引水过来进行灌溉了。当老 Jack 买完所有铺设在每块农田内部的管道的时候,老 Jack 遇到了新的难题,因为每一块农田的地势高度都不同,所以要想将两块农田的管道链接,老 Jack 就需要额外再购进跟这两块农田高度差相等长度的管道。

现在给出老 Jack农田的数据,你需要告诉老 Jack 在保证所有农田全部可连通灌溉的情况下,最少还需要再购进多长的管道。另外,每块农田都是方形等大的,一块农田只能跟它上下左右四块相邻的农田相连通。
 

Input
第一行输入一个数字T(T10),代表输入的样例组数

输入包含若干组测试数据,处理到文件结束。每组测试数据占若干行,第一行两个正整数 N,M(1N,M1000),代表老 Jack 有N行*M列个农田。接下来 N 行,每行 M 个数字,代表每块农田的高度,农田的高度不会超过100。数字之间用空格分隔。
 

Output
对于每组测试数据输出两行:

第一行输出:"Case #i:"。i代表第i组测试数据。

第二行输出 1 个正整数,代表老 Jack 额外最少购进管道的长度。
 

Sample Input
 
     
24 39 12 47 8 5632 32 4321 12 122 334 56 5612 23 4
 
Sample Output
 
     
Case #1:82Case #2:74

解除这道题的关键是看懂题意:

其实没块地的 高度差相当于相邻两个点的权值,这时候题目就转化为求连接n * m个顶点的最小代价了,有克鲁斯卡尔算法和prim算法,这里用前一种方法解:首先要求出每条边,用搜索求,这比较简单就不在解释了,求出每条边后用sort进行快排,按升序排列,在从第一条便开始挑选,用并查集来判断两个节点是否在同一棵树上,如果不是就选出这条边,并且要合拼节点,并成一棵树,有n*m各节点,就选n*m-1条边


AC代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>


const int inf = 1e6 + 10;
const int Max = 1001;
using namespace std;


int h[4] = {-1, 0, 1, 0};
int l[4] = {0, 1, 0, -1};
bool fou[Max][Max];
struct dian{
    int xu;
    int val;
}ch[Max][Max];
struct lu{
    int st, ed;
    int len;
}th[inf * 4];
int pin[inf];
//初始化数组
bool init(int n, int m)
{
    for(int i = 1; i <= n; i ++){
        for(int j = 1;j <= m ; j++){
            fou[i][j] = false;
        }
    }
    for(int i = 0; i < n * m; i ++) pin[i] = i;
}
//从下到大排
bool tmp(lu a, lu b)
{
    return a.len < b.len;
}


int Find(int son)
{
    while(son != pin[son]){
        //把当前节点变为爷爷节点的子节点
        pin[son] = pin[pin[son]];
        son = pin[son];
    }
    return son;
}
int main()
{
    int num;
    scanf("%d", &num);
    int N = 0;
    while(num --){
        int n, m, k = 0;
        scanf("%d %d", &n, &m);
        init(n, m);
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                scanf("%d", &ch[i][j].val);
                //设置节点编号
                ch[i][j].xu = k ++;
            }
        }
        k = 0;
        //求出所有的路径
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                for(int z = 0; z < 4; z ++){
                    int newx, newy;
                    newx = i + h[z];
                    newy = j + l[z];
                    if(newx >= 1 && newx <= n && newy >= 1 && newy <= m && !fou[newx][newy]){
                        th[k].st = ch[i][j].xu;
                        th[k].ed = ch[newx][newy].xu;
                        th[k ++].len = abs(ch[i][j].val - ch[newx][newy].val);
                    }
                }
                fou[i][j] = true;
            }
        }
        sort(th, th + k, tmp);
        //要找num条边
        int num = n * m - 1;
        int ans = 0;
        for(int i = 0; i < k; i ++){
                if(num <= 0)
                    break;
                //看一条边的两个顶点是否在同一棵树上
                int root1, root2;
                root1 = Find(th[i].st);
                root2 = Find(th[i].ed);
                if(root1 != root2){
                    pin[root1] = root2;
                    ans += th[i].len;
                    num --;
                }
        }
        printf("Case #%d:\n", ++N);
        cout << ans << endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值