观光之旅——最小环

题目描述

给定一张无向图,求图中一个至少包含3个点的环,环上的节点不重复,并且环上的边的长度之和最小。

该问题称为无向图的最小环问题。

你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。

输入格式
第一行包含两个整数N和M,表示无向图有N个点,M条边。

接下来M行,每行包含三个整数u,v,l,表示点u和点v之间有一条边,边长为l。

输出格式
输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出’No solution.’。

数据范围
1≤N≤100,
1≤M≤10000,
1≤l<500

输入样例:
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

输出样例:
1 3 5 2

解题思路

最优化问题——从集合的角度来思考问题:
  将所有环进行分类:环中节点编号最大值为1、环中节点编号最大值为2、…、环中节点编号最大值为n,按这种方式分为 n 类,然后问题的解即为这 n 类当中的最小值。
  那么问题就转化为:如何求解每一类的最小值?
  假设求解第 k 类的最小值,那么可以枚举节点 k 在环中的位置,即枚举 k 在节点 i 和 j 之间(i、j < k),可以画出当前环的状态如下:
在这里插入图片描述
  为了保证环的权值最小,那么就必须保证 i -> j 的路径权值最小——任意两点间距离的最小值,那么就是floyd了。

思路来源:AcWing 算法提高课

代码

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int g[N][N], d[N][N];
int res, pre[N][N];
int path[N], cnt;

void get_path(int i, int j)
{
    if (pre[i][j] == 0) return;
    
    get_path(i, pre[i][j]);
    path[cnt++] = pre[i][j];
    get_path(pre[i][j], j);
}

void floyd()
{
    memcpy(d, g, sizeof d);
    
    for (int k = 1; k <= n; ++k)
    {
        for (int i = 1; i < k; ++i)
            for (int j = i + 1; j < k; ++j)
                if ((long long)d[i][j] + g[j][k] + g[k][i] < res)
                {
                    res = d[i][j] + g[j][k] + g[k][i];
                    cnt = 0;
                    path[cnt++] = k;
                    path[cnt++] = i;
                    get_path(i, j);
                    path[cnt++] = j;
                }
        
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                if (d[i][k] + d[k][j] < d[i][j])
                {
                    d[i][j] = d[i][k] + d[k][j];
                    pre[i][j] = k;
                }
    }
}

int main()
{
    memset(g, INF, sizeof g);
    cin >> n >> m;
    int a, b, c;
    for (int i = 0; i < m; ++i)
    {
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    res = INF;
    floyd();
    
    if (res == INF) puts("No solution.");
    else 
    {
        for (int i = 0; i < cnt; ++i) cout << path[i] << ' ';
        cout << endl;
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值