算法竞赛进阶指南 观光之旅

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

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

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

输入格式

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

接下来 MM 行,每行包含三个整数 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

Floyd o(n3)

解题思路:

根据floyd算法假设当前正在枚举第k个点(floyd的最外一层循环的变量)根据floyd的性质当前只计算了经过编号为1 ~ k - 1的点而此时因为k点还未被计算因此我们可以假设以k点为固定点计算k点所在的最小环:

如何计算最小环???

因为只计算了编号为1 ~ k - 1的点所以我们可以枚举两个点i, j从i -> k -> j即为一个最小环

因为k是固定点所以i -> k, k -> j长度固定分别为g[i][k], g[k][j]

因此环的总长度为g[k][i] + d[i][j] + g[j][k]按照从k -> i -> j的顺序记录

这条路径有两个性质:

1:路径i -> k与路径k -> j不能有交集

证明假设有交集:如图

则最小值就不是从i -> k, k -> j

2:在i~j的最短道路中,一定没有环

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int pos[N][N];//记录的是从i -> j中有哪些点
int path[N], cnt;//记录的是最小环中有哪些点
int d[N][N], g[N][N];

void get_path(int i, int j)
{
    if (!pos[i][j]) return ;//说明i -> j没有任何点,即(i, j)是一条边,直接返回
    
    int k = pos[i][j];
    get_path(i, k);//按照环的顺序先记录左半边
    path[cnt ++ ] = k;
    get_path(k, j);
}

int main()
{
    cin >> n >> m;
    
    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i ++ ) g[i][i] = 0;
    
    while (m -- )
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        g[a][b] = g[b][a] = min(c, g[a][b]);
    }
    
    int res = INF;
    memcpy(d, g, sizeof g);
    for (int k = 1; k <= n; k ++ )
    {
        for (int i = 1; i < k; i ++ )//求以k为固定点至少包含3个点的环
            for (int j = i + 1; j < k; j ++ )//从i + 1开始是因为此图为双向图从i -> j和从j -> i是一样的避免重复
                if ((long long)d[i][j] + g[k][i] + g[j][k] < res)//若答案可以更新
                {
                    cnt = 0;
                    res = d[i][j] + g[k][i] + g[j][k];//记录点的顺序为k -> i -> (i -> j的中间点) -> j
                    //根据floyd算法d[i][j]记录的是从1 ~~ k - 1的最小值
                    path[cnt ++ ] = k;//按照顺序先记录k这个固定点
                    path[cnt ++ ] = i;//记录i点
                    get_path(i, j);//记录i -> j中间点不包括(i, j);
                    path[cnt ++ ] = j;//记录j点
                }
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                if (d[i][j] > d[i][k] + d[k][j])//floyd算法
                {
                    d[i][j] = d[i][k] + d[k][j];
                    pos[i][j] = k;//若可以被更新记录i -> j的中间点
                }
    }
    
    if (res == INF) puts("No solution.");
    else
    {
        for (int i = 0; i < cnt; i ++ ) printf("%d ", path[i]);
        puts("");
    }
    
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值