[POJ1734]Sightseeing 观光之旅 题解

无向图的最小环问题,前置芝士:Floyd

传送门

题目描述

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

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

想法

拿到题会想到找出所有的环,然后进行统计。

可是对于无向图而言,要找到具体的环路不是一件易事。

于是考虑如何不找环。

思路

看到数据范围:

1N100,1M10000

点数这么少,相当于直接明示用 O(n3)Floyd 算法。

整个图里跑完一遍

先看 Floyd 的核心代码:

for(int k = 1; k <= n; k ++)
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            f[i][j] = min(f[i][k], f[k][j]);

这个算法以 k 为中继点,i,j 作为附加状态,进行 dp 地状态转移。

可以发现,在最外层的循环刚开始的时候,f[i][j] 表示在经过编号 k1 的点的情况下,ij 的最小值。

我们不妨把它的过程画出来,一下是某张图的一部分。

image

那么在不经过编号 k 的点的情况下,f[i][j] 一定就指的是下面的 i...j 的路径长度。

于是,发现 min1i<j<kf[i][j]+g[i][k]+g[k][j] 即满足以下条件的一个最小环的长度:

  • 经过的点编号都不超过 k
  • 经过点 i,j,k,且 k 处于 i,j 中间并只经过一条边。

对于任意的 k[1,n],都可以在 O(n2) 的时间内计算出他所对应的所有环的长度,于是我们只需要取一个最小值即可。

而对于输出答案的限制,我们可以用一个数组 pos[i][j] 来储存在 Floyd 过程中 ij 最短路径的中继点 k

于是我们可以进行递归处理。

代码

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

using namespace std;

const int N = 110, M = 2e4 + 10;

int d[N][N], g[N][N], pos[N][N];

vector<int> path;
int n, m;

void init()
{
    memset(pos, 0, sizeof pos);
    memset(g, 0x3f, sizeof g);
    for(int i = 1; i <= n; i ++) g[i][i] = 0;
    cin >> n >> m;
    for(int i = 1; i <= m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c); // 避免重边
    }
    memcpy(d, g, sizeof g);
}

void get(int x, int y)
{
    if(x == y || !pos[x][y]) return ; // pos[x][y] == 0表示 x, y 之间没有任何点
    get(x, pos[x][y]); // 递归左边
    path.push_back(pos[x][y]); // 中间(当前点)
    get(pos[x][y], y); // 右边
}

signed main()
{
    init();

    int ans = INF;
    for(int k = 1; k <= n; k ++)
    {
        for(int i = 1; i < k; i ++)
        {
            for(int j = i + 1; j < k; j ++)
            {
                if(ans > (long long)d[i][j] + g[i][k] + g[k][j]) // 枚举k的邻点,如果i或j没有到达k的边,那么右式必然大于INF,不会考虑
                {
                    ans = d[i][j] + g[i][k] + g[k][j];
                    path.clear();
                    path.push_back(i); // 按照i -> ... -> j -> k的顺序存环,这题有spj,别急
                    get(i, j);
                    path.push_back(j);
                    path.push_back(k);
                }
            }
        }
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                if(d[i][j] > d[i][k] + d[k][j]) // 更新f数组
                    d[i][j] = d[i][k] + d[k][j],
                    pos[i][j] = k; // 记录i, j的中继点k
    } 

    if(ans == INF) puts("No solution."); // 无解情况
    else 
    {
        for(auto i : path) // 输出方案
            cout << i << ' ';
    }

    return 0;
}

双倍经验,输出 ans 即可。

posted @   MoyouSayuki  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
:name :name
点击右上角即可分享
微信分享提示