POJ - 3311 Hie with the Pie(Java & JS & Python & C)

题目来源

3311 -- Hie with the Pie (poj.org)

题目描述

Pizazz披萨店以尽可能快地将披萨送到顾客手中而自豪。

不幸的是,由于削减开支,他们只能雇一个司机来送货。

司机将等待 1 个或更多 (最多10个) 订单被下达后再开始送餐。

不用说,他想要走最短的路线来运送这些食物并返回披萨店,即使这意味着在途中不止一次地经过同一地点或披萨店。

他委托你写一个程序来帮助他。

输入描述

输入将由多个测试用例组成。

  • 第一行将包含单个整数 n,表示要送货的订单数量,其中 1 ≤ n ≤ 10。
  • 在此之后,将有 n + 1 行,每一行包含 n + 1 个整数,表示从披萨店 (编号为0) 到 n 个位置 (编号为 1 到 n)  之间的送餐时间。

    第 i 行上的第 j 个值表示直接从位置 i 到位置 j 而不经过沿途任何其他位置的时间。

请注意,由于不同的速度限制,交通信号灯等,经过其他位置的中转,可能实现更快地从 i 到 j。

此外,时间值可能不对称,即直接从位置 i 到 j 的时间可能与直接从位置 j 到 i 的时间不相同。

输入值 n = 0 将终止输入。

输出描述

对于每个测试用例,您应该输出单个数字,指示交付所有披萨并返回披萨店所需的最短时间。

用例

输入 3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
输出 8
说明

题目解析

本题第二行~倒数第二行的输入其实就是一个 (n+1) * (n+1) 的邻接矩阵,我们定义其为dist矩阵。

dist[i][j] 表示客户位置 i 到 客户位置 j 的距离。

本题需要我们帮助司机找到一条最短路径,该最短路径需要满足:

  • 从披萨店(位置0)出发,最终返回披萨店
  • 经过所有客户位置,每个客户位置可以经过不止一次

首先,这题需要我们经过所有客户位置,因此我们对 1~n 客户位置求解全排列,每一个全排列即代表一种送餐策略路径,比如n=3,则有以下送餐策略路径:

  • 1->2->3
  • 1->3->2
  • 2->1->3
  • 2->3->1
  • 3->1->2
  • 3->2->1

我们在这些全排列首尾加上0,即可得所有送餐策略路径:

  • 0->1->2->3->0
  • 0->1->3->2->0
  • 0->2->1->3->0
  • 0->2->3->1->0
  • 0->3->1->2->0
  • 0->3->2->1->0

由于本题的 1 ≤ n ≤ 10 ,因此全排列求解不会超时。

得到所有的送餐策略路径后,我们需要求解每种策略路径对应的送餐距离,

如果想要每种策略的送餐距离尽可能小,则我们应该保证路径中相邻两点之间的距离尽可能的小。

而求解图中任意两点间的最短距离,最佳策略是使用floyd算法。

关于floyd算法可以看下:最短路径算法全套(floyed+dijstra+Bellman+SPFA)_哔哩哔哩_bilibili

最终,我们基于floyd算法,求得上面每个全排列路径的最短距离,在这些最短距离中最小的即为题解。

JS算法源码

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
  while (true) {
    const n = parseInt(await readline());

    if (n == 0) break;

    // floyd算法需要基于dist和path矩阵求解
    // dist[i][j] 用于记录点 i->j 的最短距离,初始时等价于邻接矩阵
    const dist = [];
    // path[i][j] 用于记录点 i->j 最短距离情况下需要经过的中转点,初始时默认任意两点间无中转点,即默认path[i][j] = -1
    const path = [];

    for (let i = 0; i < n + 1; i++) {
      dist.push((await readline()).split(" ").map(Number));
      path.push(new Array(n + 1).fill(-1));
    }

    // floyd算法调用
    floyd();

    // ans记录经过所有点后回到出发点的最短距离
    let ans = Infinity;
    // 全排列模拟经过所有点的路径
    dfs(0, 0, new Array(n + 1).fill(false), 0);

    console.log(ans);

    // floyd算法求解图中任意两点之间的最短路径
    function floyd() {
      for (let k = 0; k < n + 1; k++) {
        for (let i = 0; i < n + 1; i++) {
          for (let j = 0; j < n + 1; j++) {
            // newDist是经过k后,i->j的距离
            const newDist = dist[i][k] + dist[k][j];
            // 如果newDist是i->j的更短路径
            if (newDist < dist[i][j]) {
              // 则更新i->j的最短距离
              dist[i][j] = newDist;
              // 且此更短距离需要经过k, path[i][j]即记录 i->j 最短距离下需要经过点 k
              path[i][j] = k;
            }
          }
        }
      }
    }

    /**
     * 找一条经过所有点的最短路径,我们可以求解所有点形成的全排列,每一个全排列都对应一条经过所有点的路径,只是经过点的先后顺序不同 //
     * 求某个全排列过程中,可以通过dist数组,累计上一个点i到下一个点j的最短路径dist[i][j]
     *
     * @param pre 上一个点, 初始为0,表示从披萨店出发
     * @param sum 当前全排列路径累计的路径权重
     * @param used 全排列used数组,用于标记哪些点已使用过
     * @param level 用于记录排列的长度
     */
    function dfs(pre, sum, used, level) {
      if (level == n) {
        // 此时pre是最后一个客户所在点,送完最后一个客户后,司机需要回到披萨店,因此最终累计路径权重为 sum + dist[pre][0]
        // 我们保留最小权重路径
        ans = Math.min(ans, sum + dist[pre][0]);
        return;
      }

      for (let i = 1; i <= n; i++) {
        if (used[i]) continue;

        used[i] = true;
        dfs(i, sum + dist[pre][i], used, level + 1);
        used[i] = false;
      }
    }
  }
})();

Java算法源码

import java.util.Scanner;

public class Main {
  static int n;
  static int[][] dist;
  static int[][] path;

  static int ans;

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    while (sc.hasNextInt()) {
      n = sc.nextInt();
      if (n == 0) break;

      // floyd算法需要基于dist和path矩阵求解
      // dist[i][j] 用于记录点 i->j 的最短距离,初始时等价于邻接矩阵
      dist = new int[n + 1][n + 1];
      // path[i][j] 用于记录点 i->j 最短距离情况下需要经过的中转点,初始时默认任意两点间无中转点,即默认path[i][j] = -1
      path = new int[n + 1][n + 1];

      for (int i = 0; i < n + 1; i++) {
        for (int j = 0; j < n + 1; j++) {
          dist[i][j] = sc.nextInt();
          path[i][j] = -1;
        }
      }

      // floyd算法调用
      floyd();

      // ans记录经过所有点后回到出发点的最短距离
      ans = Integer.MAX_VALUE;
      // 全排列模拟经过所有点的路径
      dfs(0, 0, new boolean[n + 1], 0);

      System.out.println(ans);
    }
  }

  // floyd算法求解图中任意两点之间的最短路径
  public static void floyd() {
    for (int k = 0; k < n + 1; k++) {
      for (int i = 0; i < n + 1; i++) {
        for (int j = 0; j < n + 1; j++) {
          // newDist是经过k后,i->j的距离
          int newDist = dist[i][k] + dist[k][j];
          // 如果newDist是i->j的更短路径
          if (newDist < dist[i][j]) {
            // 则更新i->j的最短距离
            dist[i][j] = newDist;
            // 且此更短距离需要经过k, path[i][j]即记录 i->j 最短距离下需要经过点 k
            path[i][j] = k;
          }
        }
      }
    }
  }

  /**
   * 找一条经过所有点的最短路径,我们可以求解所有点形成的全排列,每一个全排列都对应一条经过所有点的路径,只是经过点的先后顺序不同 //
   * 求某个全排列过程中,可以通过dist数组,累计上一个点i到下一个点j的最短路径dist[i][j]
   *
   * @param pre 上一个点, 初始为0,表示从披萨店出发
   * @param sum 当前全排列路径累计的路径权重
   * @param used 全排列used数组,用于标记哪些点已使用过
   * @param level 用于记录排列的长度
   */
  public static void dfs(int pre, int sum, boolean[] used, int level) {
    if (level == n) {
      // 此时pre是最后一个客户所在点,送完最后一个客户后,司机需要回到披萨店,因此最终累计路径权重为 sum + dist[pre][0]
      // 我们保留最小权重路径
      ans = Math.min(ans, sum + dist[pre][0]);
      return;
    }

    for (int i = 1; i <= n; i++) {
      if (used[i]) continue;

      used[i] = true;
      dfs(i, sum + dist[pre][i], used, level + 1);
      used[i] = false;
    }
  }
}

Python算法源码

import sys


# floyd算法求解图中任意两点之间的最短路径
def floyd():
    for k in range(n + 1):
        for i in range(n + 1):
            for j in range(n + 1):
                # newDist是经过k后,i->j的距离
                newDist = dist[i][k] + dist[k][j]
                # 如果newDist是i->j的更短路径
                if newDist < dist[i][j]:
                    # 则更新i->j的最短距离
                    dist[i][j] = newDist
                    # 且此更短距离需要经过k, path[i][j]即记录 i->j 最短距离下需要经过点 k
                    path[i][j] = k


def dfs(pre, sumDis, used, level):
    """
    找一条经过所有点的最短路径,我们可以求解所有点形成的全排列,每一个全排列都对应一条经过所有点的路径,只是经过点的先后顺序不同 //
    求某个全排列过程中,可以通过dist数组,累计上一个点i到下一个点j的最短路径dist[i][j]
    :param pre: 上一个点, 初始为0,表示从披萨店出发
    :param sumDis: 当前全排列路径累计的路径权重
    :param used: 全排列used数组,用于标记哪些点已使用过
    :param level: 用于记录排列的长度
    """
    global ans

    if level == n:
        # 此时pre是最后一个客户所在点,送完最后一个客户后,司机需要回到披萨店,因此最终累计路径权重为 sum + dist[pre][0]
        # 我们保留最小权重路径
        ans = min(ans, sumDis + dist[pre][0])
        return

    for i in range(1, n + 1):
        if used[i]:
            continue

        used[i] = True
        dfs(i, sumDis + dist[pre][i], used, level + 1)
        used[i] = False


while True:
    n = int(input())

    if n == 0:
        break

    # floyd算法需要基于dist和path矩阵求解
    # dist[i][j] 用于记录点 i->j 的最短距离,初始时等价于邻接矩阵
    dist = [list(map(int, input().split())) for _ in range(n + 1)]
    # path[i][j] 用于记录点 i->j 最短距离情况下需要经过的中转点,初始时默认任意两点间无中转点,即默认path[i][j] = -1
    path = [[-1] * (n + 1) for _ in range(n + 1)]

    # floyd算法调用
    floyd()

    # ans记录经过所有点后回到出发点的最短距离
    ans = sys.maxsize
    # 全排列模拟经过所有点的路径
    dfs(0, 0, [False] * (n + 1), 0)

    print(ans)

C算法源码

POJ的C编译器估计有点老了,很多语法和内置库都无法用,下面是适配修改后可以AC的代码

#include 
#include 

#define MIN(a,b) ((a) < (b) ? (a) : (b))

#define MAX_SIZE 11

int n;
int dist[MAX_SIZE][MAX_SIZE];
int path[MAX_SIZE][MAX_SIZE];

int ans;
int used[MAX_SIZE];

/**
   * 找一条经过所有点的最短路径,我们可以求解所有点形成的全排列,每一个全排列都对应一条经过所有点的路径,只是经过点的先后顺序不同 //
   * 求某个全排列过程中,可以通过dist数组,累计上一个点i到下一个点j的最短路径dist[i][j]
   *
   * @param pre 上一个点, 初始为0,表示从披萨店出发
   * @param sum 当前全排列路径累计的路径权重
   * @param used 全排列used数组,用于标记哪些点已使用过
   * @param level 用于记录排列的长度
   */
void dfs(int pre, int sum, int used[], int level) {
    int i;

    if (level == n) {
        // 此时pre是最后一个客户所在点,送完最后一个客户后,司机需要回到披萨店,因此最终累计路径权重为 sum + dist[pre][0]
        // 我们保留最小权重路径
        ans = MIN(ans, sum + dist[pre][0]);
        return;
    }

    for (i = 1; i <= n; i++) {
        if (used[i]) continue;

        used[i] = 1;
        dfs(i, sum + dist[pre][i], used, level + 1);
        used[i] = 0;
    }
}

// floyd算法求解图中任意两点之间的最短路径
void floyd() {
    int i, j, k;

    for (k = 0; k < n + 1; k++) {
        for (i = 0; i < n + 1; i++) {
            for (j = 0; j < n + 1; j++) {
                // newDist是经过k后,i->j的距离
                int newDist = dist[i][k] + dist[k][j];
                // 如果newDist是i->j的更短路径
                if (newDist < dist[i][j]) {
                    // 则更新i->j的最短距离
                    dist[i][j] = newDist;
                    // 且此更短距离需要经过k, path[i][j]即记录 i->j 最短距离下需要经过点 k
                    path[i][j] = k;
                }
            }
        }
    }
}

int main() {
    while (1) {
        int i, j;

        scanf("%d", &n);

        if (n == 0) {
            break;
        }

        // floyd算法需要基于dist和path矩阵求解
        for (i = 0; i < n + 1; i++) {
            for (j = 0; j < n + 1; j++) {
                // dist[i][j] 用于记录点 i->j 的最短距离,初始时等价于邻接矩阵
                scanf("%d", &dist[i][j]);
                // path[i][j] 用于记录点 i->j 最短距离情况下需要经过的中转点,初始时默认任意两点间无中转点,即默认path[i][j] = -1
                path[i][j] = -1;
            }
        }

        // floyd算法调用
        floyd();

        // ans记录经过所有点后回到出发点的最短距离
        ans = INT_MAX;

        // 全排列模拟经过所有点的路径
        for (i = 0; i < n + 1; i++) used[i] = 0;
        dfs(0, 0, used, 0);

        printf("%d\n", ans);
    }
}

你可能感兴趣的:(算法与数据结构,Java,JavaScript,Python,C语言,POJ-3311)