图论 - 最小生成树 - Kurskal算法 - 道路升级

Posted 风语轻轻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论 - 最小生成树 - Kurskal算法 - 道路升级相关的知识,希望对你有一定的参考价值。

问题描述

Z国有 n 个城市和 m 条双向道路,每条道路连接了两个不同的城市,保证所有城市之间都可以通过这些道路互达。每条道路都有一个载重量限制,这限制了通过这条道路的货车最大的载重量。道路的编号从 1 至 m 。巧合的是,所有道路的载重量限制恰好都与其编号相同。

现在,要挑选出若干条道路,将它们升级成高速公路,并满足如下要求:

  • 所有城市之间都可以通过高速公路互达。
  • 对于任意两个城市 u,v 和足够聪明的货车司机:只经过高速公路从 u 到达 v 能够装载货物的最大重量,与经过任意道路从 u 到达 v 能够装载货物的最大重量相等。(足够聪明的司机只关注载重量,并不在意绕路)

在上面的前提下,要求选出的道路数目尽可能少。

求需要挑选出哪些道路升级成高速公路(如果有多种方案请任意输出一种)。

输入

第一行 2 个用空格隔开的整数 n,m ,分别表示城市数目、道路数目。

第 2 行到第 m+1 行,每行 2 个用空格隔开的整数 u,v 描述一条从 u 到 v 的双向道路,第 i+1 行的道路的编号为 i 。

注意:数据只保证不存在连接的城市相同的道路(自环),并不保证不存在两条完全相同的边(重边)

输出

第一行一个整数 k ,表示升级成高速公路的道路数。

接下来 k 行每行一个整数,从小到大输出所有选出的道路的编号。

输入样例

3 3
1 2
2 3
1 3

输出样例

2
2
3

数据范围

对于 20% 的数据,保证 n≤5,m≤10。

对于 60% 的数据,保证 n≤1,000,m≤5,000。

对于 100% 的数据,保证 n≤200,000,m≤400,000。

时间限制:10 sec

空间限制:256 MB

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

思路:

       此题为最小生成树算法的推广习题,其实是求解最大生成树。

       问题抽象:将所有城市看作结点,道路看作边,载重量看作边的权值,则所有城市和道路构成一张连通带权图。根据题目要求,我们可知,题目要从所有给出的边中选择出一些边,这些边要满足以下要求: ① 这些边使所有结点构成一连通图。② 该连通图中任意两个结点间存在一条通路,且该通路上边的权值和是所有原图中这两个点间所有可能通路中路径权值最大的。③ 满足前两条前提下所选择的边尽可能少。

      该问题其实是求解原图的一棵最大生成树。类似最小生成树,可用Kurskal算法求解。

C++代码:

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

const int MAX_ROAD = 400001; 
int nl[MAX_ROAD], nr[MAX_ROAD];    // nl[i], nr[i]分别记录第i条道路的两个端点 

/* 并查集 */ 
class DisjointSet
{
    private:
        unsigned int size;   // 并查集中所有端点数,集合中元素分别为 1 ~ n。 
        int * a;    // 用于保存并查集,a[i] = j (j > 0) 表示元素i所在集合的父结点是元素j;若a[i] = -k,表示元素i所在的集合其为根,其集合大小为k。 
    public:
        DisjointSet() { size = 0; a = NULL; }
        DisjointSet(unsigned int _size)
        {
            size = _size;
            a = new int[size+1];
            memset(a, -1, (size+1)*sizeof(int));    // 初始化各元素各成一个集合,且集合大小均为1。 
        }
        ~DisjointSet() { delete [] a; }
        int find(int x)    // 寻找编号为x的元素所在的集合的根,若返回-1表示没找到。 
        {
            if ( x <= 0 || x > size )    // 若x超出合法范围 
                return -1;
            if ( a[x] < 0 )    // x即为根 
                return x;
            a[x] = find(a[x]); // 路径压缩 
            return a[x];
        } 
        bool unionset(int x, int y)    // 将编号为x和y的元素所在的集合合并,成功则返回true,否则返回false。 
        {
            int rx = find(x);
            int ry = find(y);
            if ( rx < 0 || ry < 0 || rx == ry )    // 有任意元素查找失败,或者【两个元素已在同一集合中】则什么也不做
                return false;
            if ( a[rx] < a[ry]  )    // x所在集合规模大
            {
                a[rx] += a[ry];
                a[ry] = rx; 
            }
            else
            {
                a[ry] += a[rx];
                a[rx] = ry;
            }
            return true;
        } 
}; 

int main()
{
    int n_city = 0, n_road = 0;    // 城市数,道路数
    scanf("%d %d", &n_city, &n_road);    // 读入城市数,道路数 
    
    for ( int i = 1; i <= n_road; ++i )  // 读入道路i连接的城市 
        scanf("%d %d", nl+i, nr+i); 
    
    DisjointSet c(n_city);
    int n_choice = 0;   // 选择的道路数 
    vector<int> ans;    // 存储选择的道路编号,编号从大到小。 
    
    /* Kruskal算法求最大生成树,由于此题道路权重即编号,故自然有序,不需要用堆存储道路 */ 
    for ( int i = n_road; n_choice < n_city && i > 0; --i )    // 若n_choice = n_city - 1,则已得到最大生成树,不必继续做下去。 
    {
        int sl = c.find(nl[i]), sr = c.find(nr[i]);    // 找到道路两个端点的根
        if ( sl != sr )    // 若道路左右两个城市不在一个集合,则它们选择该边,并将两个集合合并 
        {
            ++n_choice;
            ans.push_back(i);
            c.unionset(sl, sr);
        } 
    } 
    
    printf("%d\n", n_choice);
    for ( int i = n_choice - 1; i >=0; --i )
        printf("%d\n", ans[i]); 
    return 0;
} 

 

 

以上是关于图论 - 最小生成树 - Kurskal算法 - 道路升级的主要内容,如果未能解决你的问题,请参考以下文章

Prim算法和Kruskal算法(图论中的最小生成树算法)

图论算法零基础最小生成树学习与总结

经典树与图论(最小生成树、哈夫曼树、最短路径问题---Dijkstra算法)

图论-----最小生成树

图论讲解——最小生成树

图论最小生成树Prim和Kruskal算法