现在的位置: 首页 > 综合 > 正文

HDOJ 1233 还是畅通工程(并查集、kruscal算法 + prim)

2019年02月13日 ⁄ 综合 ⁄ 共 2486字 ⁄ 字号 评论关闭

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1233

这是我第一次接触到算法~~想想还有点小激动诶~

这个题就是一个简单的最小生成树。

会想到去写这道题是因为我写了1232,那是一个简单的并查集的题目。水过1232之后,就顺便看了看1233。结果,这题纠结了我好久啊!!抓狂!!后面龙酱帮我捉虫的时候才发现竟然是并查集写错了。。重写一遍1232去QAQ 其实是因为不理解并查集啦。。当时死记硬背,只是把并查集的框架背了下来,结果背错了ORZ..

1233题因为是用的kruskal,所以会用到并查集。最小生成树还可以用prime算法做,但是我还没看0.0

so,what is bingchaji?(请首先阅读并查集的百度百科或者维基百科)

首先并查集有个father数组存储每个元素的元祖。father数组的下标代表元素,里头则存储着他们的老爸(当然老爸和自己是一个元祖)或者元祖,比如元素i的祖先是j,则表示为father[i] = j;如果该元素就是元祖,则存储自己,如father[j] = j。

然后还有一个find函数,用来寻找元祖的。

int find(int i)
{
    if (i == father[i])
        return i; //找到元祖
    else
        return father[i] = find(father[i]); //存储数与下标不相等,还没有找到元祖~~~
}

似乎还可以路径压缩,下次琢磨一下。

kruskal算法就是先把边存起来,然后按边权排序,再利用并查集查询是否同一元祖(或者两个朋友or亲戚有没有关系、能不能从别的路径走通两个村庄)按照顺序来一个个挑选是否构建这条边。如果同意元祖,不构建;若非同一元祖,构建。


因为已经按边权排序,所以构建边的时候边权是递增的,当所有元素都连接时,总边权应为最小(因为是贪心,所以如果有那种比较厉害的数据可能过不了)。


下面是ac代码:

#include<stdio.h>
#include<stdlib.h>
 
int father[100];
//用结构体来做方便排序
struct node
{
    int u; //村庄一号
    int v; //村庄二号
    int dis; //公路长,即边权
}x[5000];
 
int find(int i)
{
    if (i == father[i])
        return i; //找到元祖
    else
        return father[i] = find(father[i]); //存储数与下标不相等,还没有找到元祖
}
 
int cmp(const void *a, const void *b) //qsort的比较函数,这个表示从小到大排。
{
    return (*(node *)a).dis > (*(node *)b).dis ? 1 : -1; //下次要排别的变量,只要改掉node和.dis就可以了。至于为什么是1和-1的返回值,不知道,背的..T^T
}
 
int main()
{
    int N;
     
    x[0].dis = 0; //防止这个不用的dis因为没初始化结果在排序的时候乱入
     
    //freopen("1233.txt", "r", stdin);
     
    while(scanf("%d", &N) != EOF && N)
    {
        int i;
        int road; //总共可修的公路条数
        int sum = 0; //总边权
        int m = 0; //实际修路数,计数用
         
        road = N * (N-1) / 2;
        sum = 0;
         
        for (i = 1; i <= N; i++)
            father[i] = i; //初始化father数组
         
        for (i = 1; i <= road; i++)
            scanf("%d%d%d", &x[i].u, &x[i].v, &x[i].dis); //储存边
         
        qsort(x+1, road, sizeof(x[0]), cmp); //按边权排序
         
        for (i = 1; i <= road ;i++)
        {
            int s1 = find(x[i].u);
            int s2 = find(x[i].v);
            if (s1 != s2) //如果两个村庄不修路就无法联通,即元祖不同
            {
                father[s1] = father[s2]; //把前者的元祖的father元素里存的自己改成后者的元祖
                sum += x[i].dis; 
                m++; //修成了一条路!
            }
        }
        printf("%d\n", sum);
    }
    return 0;
}

补上prim的解法:

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 1010

int cost[MAXN][MAXN];
bool vis[MAXN];
int low[MAXN];
int u, v, c;

int prim(int cost[][MAXN], int n)
{
    int ans = 0;
    memset(vis, false, sizeof(vis));
    vis[0] = true;
    for(int i = 1; i < n; i++)
        low[i] = cost[0][i];
    for(int i = 1; i < n; i++)
    {
        int minc = INF;
        int p = -1;
        for(int j = 0; j < n; j++)
            if((!vis[j]) && (minc > low[j]))
            {
                minc = low[j];
                p = j;
            }
        if(minc >= INF)
            return -1;
        ans += minc;
        vis[p] = true;
        for(int j = 0; j < n; j++)
            if((!vis[j]) && (low[j] > cost[p][j]))
                low[j] = cost[p][j];
    }
    return ans;
}

int main()
{
//    freopen("1233.in", "r", stdin);

    int n;
    while(scanf("%d", &n), n)
    {
        int se = (n - 1) * n / 2;
        memset(cost, 0x3f, sizeof(cost));
        for(int i = 0; i < se; i++)
        {
            scanf("%d%d%d", &u, &v, &c);
            cost[u-1][v-1] = c;
            cost[v-1][u-1] = c;
        }
        printf("%d\n", prim(cost, n));
    }
    return 0;
}

抱歉!评论已关闭.