You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 344. 观光之旅

一、题目描述

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

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

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

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

接下来 M 行,每行包含三个整数 uvl,表示点 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 + dp 求最小环模板(最少三点)

floyd插点 算法,在点k插入前 可计算i->x>j,x \in [1 \sim k-1]这样的最短路,当然,也可以不选择任何一个中间点,dist[i][j]天生最小。

枚举所有以k为环中 最大节点 的环即可。

解释k是从1\sim n的,说它是最大节点,是指每次插入的节点号最大,并不表示在环中它一定比i,j还大。

三、floyd+dp

#include <bits/stdc++.h>

using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dist[N][N];
int path[N], idx;
int mid[N][N];
int ans = INF;

// i->j之间的最短路径中途经点有哪些
void get_path(int i, int j) {
    int k = mid[i][j]; // 获取中间转移点
    if (!k) return;    // 如果i,j之间没有中间点停止
    get_path(i, k);    // 递归前半段
    path[idx++] = k;   // 记录k节点
    get_path(k, j);    // 递归后半段
}

int main() {
    // n个顶点m条边
    scanf("%d %d", &n, &m);

    // 初始化邻接矩阵
    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i++) g[i][i] = 0; // 邻接矩阵自己到自己距离是0

    while (m--) {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c); // 求最短路之类,(a,b)之间多条边输入只保留最短边
    }

    // 把原始地图复制出来到生成最短距离dist
    memcpy(dist, g, sizeof dist);

    for (int k = 1; k <= n; k++) { // 枚举每一个引入点k来连接缩短i,j的距离
        /*
        Q1:为什么循环的时候i和j都需要小于k?
        A:为了避免经过相同的点比如i == k时三个点就变成两个点了。
        其实循环到n也是可以的不过当i, j, k中有两个相同时就要continue一下

        Q2:为什么非得把DP的这段代码嵌入到Floyd的整体代码中不能先Floyd后再进行DP吗
        A:是不可以的。因为在进行插入节点号为k时其实dist[i][j]中记录的是1~k-1插点后的最小距离
        而不是全部插入点后的最短距离。
        */
        for (int i = 1; i < k; i++)
            for (int j = i + 1; j < k; j++)
                if (g[i][k] + g[k][j] < ans - dist[i][j]) { // 减法防止爆INT
                    ans = dist[i][j] + g[i][k] + g[k][j];
                    // 找到更小的环,需要记录路径,并且要求: 最小环的所有节点(按顺序输出)
                    //  顺序
                    //  1. 上面的i,j枚举逻辑是j>i,所以i是第一个
                    //  2. i->j 中间的路线不明需要用get_path进行查询出i->j的最短路径怎么走,当然,也是在<k的范围内的
                    //  3. 记录j
                    //  4. 记录k
                    idx = 0;
                    path[idx++] = i;
                    get_path(i, j); // i是怎么到达j的就是问dist[i][j]是怎么获取到的,这是在求最短路径过程中的一个路径记录问题
                    path[idx++] = j;
                    path[idx++] = k;
                }

        // 正常floyd
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (dist[i][j] > dist[i][k] + dist[k][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    mid[i][j] = k; // 记录路径i->j 是通过k进行转移的
                }
    }

    if (ans == INF)
        puts("No solution.");
    else
        for (int i = 0; i < idx; i++) cout << path[i] << ' ';

    return 0;
}