EOJ 2165 寻找航海路线 非严格次小生成树

题目描述

茫茫大海上有许多小岛,为了避免不可预知的危险,人们只能在两岛之间直线行船。先辈们找到了一个岛之间的最佳航海路线,所谓最佳航海路线,就是能够连接所有的岛且路线长度总和最小。但随着航海业的日益发展,这条路线变得日益拥挤,于是海运局聘请你寻找另外一个尽可能佳的路线,所经过的小岛与原路线不完全相同就可。

输入格式

有多组测试数据。第一行两个整数 N,M (2<=N<=500),表示有 N 个岛,编号 1 到 n。之后 M 行,每行 3 个整数 u,v,w,表示从 u 岛到 v 岛的直线长度为 w。

输出格式

输出一行两个数,原路线长度和与新路线长度和,如果无法找到新路线,输出-1。

样例

input

4 6
1 2 2
2 3 2
3 4 2
4 1 2
1 3 1
2 4 1
3 2
1 2 2
2 3 2

output

4 4
4 -1

思路:题目要求输出的答案其实是最小生成树的长度以及(非严格)次小生成树长度。次小生成树是在最小生成树的基础上,遍历所有未被选中的边,尝试着将其加入最小生成树,并删去由此产生的环中除了新加入的这条边外最长的那条边(即留下的边都是最短的那些,保证新的树长度尽可能小),选取遍历结果中长度最小的树,即为次小生成树,由于这样产生的树长度可能与最小生成树相同,因此这种次小生成树是非严格的,但题目描述的正是这种非严格的次小生成树。次小生成树算法可以由Prim算法改良,需要额外的数组记录未被选中的边集,以及每两个点之间最长的边(用动态规划思想实现),代码如下:

 

#include <iostream>
#include <cstring>
#define INF 0x3f3f3f3f

using namespace std;

/*Prim算法需要的全局变量*/
bool visited [501];
int cost[501][501];
int pre[501];
int lowc[501];
/*Prim算法需要的全局变量*/
/*次小生成树需要的全局变量*/
bool used[501][501]; //标记边是否被用在最小生成树中
int max_edge[501][501]; //记录两个点之间最长的边的长度
/*次小生成树需要的全局变量*/


int Prim(int n)
{
    int total = 0, index;
    for(int i=2; i<=n; ++i){
        lowc[i] = cost[1][i];
        if(lowc[i]<INF)
            pre[i] = 1;
    }
    pre[1] = 0;
    visited[1] = true;
    for(int i=2; i<=n; ++i){
        int min_e = INF;
        for(int j=2; j<=n; ++j){
            if(!visited[j] && lowc[j]<min_e){
                min_e = lowc[j];
                index = j;
            }
        }
        total += lowc[index];
        visited[index] = true;
        used[index][pre[index]] = used[pre[index]][index] = true; //标记这条边用过了
        for(int j=2; j<=n; ++j){
            if(!visited[j] && cost[index][j]<lowc[j]){
                lowc[j] = cost[index][j];
                pre[j] = index;
            }
            if(visited[j] && j!=index) //更新最长边
                max_edge[index][j] = max_edge[j][index] = max(max_edge[pre[index]][j], lowc[index]);
        }
    }
    return total;
}


int second_st(int n, int MST)
{
    int total = INF;
    /*遍历所有边*/
    for(int i=1; i<=n; ++i)
        for(int j=i+1; j<=n; ++j){
            if(!used[i][j] && cost[i][j]<INF)
                total = min(MST-max_edge[i][j]+cost[i][j], total);
        }
    if(total == INF) //找不到次小生成树
        total = -1;
    return total;
}


int main()
{
    int N, M, u, v, w, MST, SMT;
    while(cin>>N>>M){
        memset(visited, 0, sizeof(visited));
        memset(max_edge, 0, sizeof(max_edge));
        memset(used, 0, sizeof(used));
        for(int i=1; i<=N; ++i)
            for(int j=1; j<=N; ++j)
                cost[i][j] = INF;
        for(int i=0; i<M; ++i){
            cin>>u>>v>>w;
            cost[u][v] = w;
            cost[v][u] = w;
        }
        MST = Prim(N);
        SMT = second_st(N, MST);
        cout<<MST<<' '<<SMT<<endl;
    }
    return 0;
}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值