PEEK143 郊区春游

题目链接

PEEK143 郊区春游

题目描述

给定一个由 个郊区和 条无向道路组成的城市地图,每条道路有通行费用。现在需要从中挑选 个指定的郊区进行游玩。游玩顺序可以任意排列,但必须访问完所有 个郊区。总费用等于游玩过程中,按顺序访问的相邻两个郊区之间的最短路径长度之和。请求出最小可能的总费用。

解题思路

本题的实质是寻找一个访问 个指定节点的最优顺序,使得总路径成本最低。这是一个经典的旅行商问题 (Traveling Salesperson Problem, TSP) 的变体。由于 的值很小(通常 ),我们可以使用状态压缩动态规划 (State Compression DP) 来解决。

核心步骤

  1. 预处理:计算全源最短路

    • 题目中的通行费用是任意两个游玩点之间的最短路径长度,而不是直接的边权。因此,第一步是计算出图中任意两个节点之间的最短距离。

    • 我们可以使用 Floyd-Warshall 算法来计算全源最短路。该算法的时间复杂度为 ,在 较小的情况下(如 )是完全可行的。

    • 在得到 矩阵后,我们可以只关注 个指定郊区之间的距离,相当于构建了一个新的、只包含这 个点的完全图。

  2. 状态压缩 DP 求解 TSP

    • 状态定义

      • : 一个 位的位掩码 (bitmask)。 的二进制表示中,第 位为 1 代表第 个指定郊区已经被访问过,为 0 则表示未访问。

      • : 当前路径的终点是第 个指定郊区。

      • 的值:表示访问了 中标记的所有郊区,并且以第 个郊区作为结束点的最短路径总长度

    • 状态转移

      为了计算 ,我们需要枚举路径中倒数第二个访问的郊区 。这个 必须是 中除了 之外的另一个郊区。

      • 其中, 是一个位运算,表示从集合 中去掉 这个元素。 是第 个和第 个指定郊区的原始编号, 是它们之间的最短路程。

      • 这个转移方程的含义是:要到达状态 ,我们可以从任何一个已经访问了 中除 之外所有点、且终点为 的状态 出发,然后从 走到

    • 初始状态

      对于所有 个指定郊区,它们都可以作为旅行的起点。因此,对于第 个指定郊区(),。这表示访问单个郊区 ,并以它为终点,路径长度为 0。

    • 最终答案

      在填充完整个 DP 表之后, 存储了访问完所有 个郊区,并以第 个郊区为终点的最短路径长度。我们只需要在所有可能的终点 中取一个最小值,即 for

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int INF = 1e9;

int main() {
    int n, m, k;
    cin >> n >> m >> k;

    vector<int> p(k);
    for (int i = 0; i < k; ++i) {
        cin >> p[i];
    }

    vector<vector<int>> dist(n + 1, vector<int>(n + 1, INF));
    for (int i = 1; i <= n; ++i) {
        dist[i][i] = 0;
    }

    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        dist[u][v] = min(dist[u][v], w);
        dist[v][u] = min(dist[v][u], w);
    }

    // Floyd-Warshall 算法
    for (int l = 1; l <= n; ++l) {
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (dist[i][l] != INF && dist[l][j] != INF) {
                    dist[i][j] = min(dist[i][j], dist[i][l] + dist[l][j]);
                }
            }
        }
    }

    // 动态规划
    vector<vector<int>> dp(1 << k, vector<int>(k, INF));

    for (int i = 0; i < k; ++i) {
        dp[1 << i][i] = 0;
    }

    for (int s = 1; s < (1 << k); ++s) {
        for (int i = 0; i < k; ++i) {
            if ((s >> i) & 1) { // 如果 i 在集合 s 中
                if (dp[s][i] == INF) continue;
                for (int j = 0; j < k; ++j) {
                    if (!((s >> j) & 1)) { // 如果 j 不在集合 s 中
                        int next_s = s | (1 << j);
                        int u = p[i];
                        int v = p[j];
                        if (dist[u][v] != INF) {
                            dp[next_s][j] = min(dp[next_s][j], dp[s][i] + dist[u][v]);
                        }
                    }
                }
            }
        }
    }

    int min_cost = INF;
    int final_state = (1 << k) - 1;
    for (int i = 0; i < k; ++i) {
        min_cost = min(min_cost, dp[final_state][i]);
    }

    cout << min_cost << endl;

    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int k = sc.nextInt();

        int[] p = new int[k];
        for (int i = 0; i < k; i++) {
            p[i] = sc.nextInt();
        }

        int INF = 1_000_000_000;
        int[][] dist = new int[n + 1][n + 1];
        for (int i = 0; i <= n; i++) {
            Arrays.fill(dist[i], INF);
            dist[i][i] = 0;
        }

        for (int i = 0; i < m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            int w = sc.nextInt();
            dist[u][v] = Math.min(dist[u][v], w);
            dist[v][u] = Math.min(dist[v][u], w);
        }

        // Floyd-Warshall 算法
        for (int l = 1; l <= n; l++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (dist[i][l] != INF && dist[l][j] != INF) {
                        dist[i][j] = Math.min(dist[i][j], dist[i][l] + dist[l][j]);
                    }
                }
            }
        }

        int[][] dp = new int[1 << k][k];
        for (int i = 0; i < (1 << k); i++) {
            Arrays.fill(dp[i], INF);
        }

        for (int i = 0; i < k; i++) {
            dp[1 << i][i] = 0;
        }

        for (int s = 1; s < (1 << k); s++) {
            for (int i = 0; i < k; i++) {
                if ((s & (1 << i)) != 0) { // 如果 i 在集合 s 中
                    if (dp[s][i] == INF) continue;
                    for (int j = 0; j < k; j++) {
                        if ((s & (1 << j)) == 0) { // 如果 j 不在集合 s 中
                            int nextS = s | (1 << j);
                            int u = p[i];
                            int v = p[j];
                            if (dist[u][v] != INF) {
                                dp[nextS][j] = Math.min(dp[nextS][j], dp[s][i] + dist[u][v]);
                            }
                        }
                    }
                }
            }
        }

        int minCost = INF;
        int finalState = (1 << k) - 1;
        for (int i = 0; i < k; i++) {
            minCost = Math.min(minCost, dp[finalState][i]);
        }

        System.out.println(minCost);
    }
}
n, m, k = map(int, input().split())
p = list(map(int, input().split()))

INF = float('inf')
dist = [[INF] * (n + 1) for _ in range(n + 1)]

for i in range(1, n + 1):
    dist[i][i] = 0

for _ in range(m):
    u, v, w = map(int, input().split())
    dist[u][v] = min(dist[u][v], w)
    dist[v][u] = min(dist[v][u], w)

# Floyd-Warshall 算法
for l in range(1, n + 1):
    for i in range(1, n + 1):
        for j in range(1, n + 1):
            if dist[i][l] != INF and dist[l][j] != INF:
                dist[i][j] = min(dist[i][j], dist[i][l] + dist[l][j])

dp = [[INF] * k for _ in range(1 << k)]

for i in range(k):
    dp[1 << i][i] = 0

for s in range(1, 1 << k):
    for i in range(k):
        if (s >> i) & 1:
            if dp[s][i] == INF:
                continue
            for j in range(k):
                if not ((s >> j) & 1):
                    next_s = s | (1 << j)
                    u, v = p[i], p[j]
                    if dist[u][v] != INF:
                        dp[next_s][j] = min(dp[next_s][j], dp[s][i] + dist[u][v])

min_cost = INF
final_state = (1 << k) - 1
for i in range(k):
    min_cost = min(min_cost, dp[final_state][i])

print(min_cost)

算法及复杂度

  • 算法:Floyd-Warshall + 状态压缩动态规划。

  • 时间复杂度

    • Floyd-Warshall 算法:
    • 状态压缩 DP:
    • 总时间复杂度为
  • 空间复杂度

    • 矩阵:
    • 表:
    • 总空间复杂度为