(C卷,200分)- 路口最短时间问题(Java & JS & Python & C)

题目描述

假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 timePerRoad;

街道的街口(交叉点)有交通灯,灯的周期 T(=lights[row][col])各不相同;

车辆可直行、左转和右转,其中直行和左转需要等相应 T 时间的交通灯才可通行,右转无需等待。

现给出 n * m 个街口的交通灯周期,以及起止街口的坐标,计算车辆经过两个街口的最短时间。

其中:

  1. 起点和终点的交通灯不计入时间,且可以在任意方向经过街口
  2. 不可超出 n * m 个街口,不可跳跃,但边线也是道路(即:lights[0][0] -> lights[0][1] 是有效路径)

入口函数定义:

/**
 * @param lights:n*m个街口每个交通灯的周期,值范围[0, 120],n和m的范围为[1,9]
 * @param timePreRoad:相邻两个街口之间街道的通行时间,范围为[0,600]
 * @param rowStart:起点的行号
 * @param colStart:起点的列号
 * @param rowEnd:终点的行号
 * @param colEnd:终点的列号
 * @return lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
 */
int calcTime(int[][] lights, int timePreRoad, int rowStart, int colStart, int rowEnd, int colEnd)

输入描述

第一行输入 n 和 m,以空格分隔

之后 n 行输入 lights矩阵,矩阵每行m个整数,以空格分隔

之后一行输入 timePerRoad

之后一行输入 rowStart colStart,以空格分隔

最后一行输入 rowEnd colEnd,以空格分隔

输出描述

lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间

用例

输入 3 3
1 2 3
4 5 6
7 8 9
60
0 0
2 2
输出 245
说明 行走路线为 (0,0) -> (0,1) -> (1,1) -> (1,2) -> (2,2) 走了4格路,2个右转,1个左转,共耗时 60+0+60+5+60+0+60=245

题目解析

用例图示如下:

(0,0)是起点,(2,2)是终点

上面红色路径是起点到终点的最短时间路径,各线段花费时间如下:

  • (0,0) → (0,1) 由于是初始,因此不需要等待,仅花费 timePerRoad = 60 单位时间
  • (0,1) → (1,1) 发生了右拐,因此不需要等待,仅花费 timePerRoad = 60 单位时间
  • (1,1) → (1,2) 发生了左拐,因此需要等待,花费时间为 timePerRoad + lights[1][1] = 60 + 5 单位时间
  • (1,2) → (2,2) 发生了右拐,因此不需要等待,仅花费 timePerRoad = 60 单位时间

最终总耗时为:60 + 60 + (60 + 5) + 60 = 245

本题看上去是单源最短路径问题,但是实际操作起来是不可以的

比如基于题目用例,我们通过Dijistra算法模拟,过程如下:

初始定义一个dist数组,dist[i][j]用于记录 起点 到 (i, j)点 的最短时间。

初始时,起点到达自身位置的时间为0,到达其余点的时间为无限大MAX,如下图所示:

然后我们从起点出发,此时可以向上下左右四个方向探索:

由于探索位置不能越界,因此当前用例起点只能向下和向右探索,由于是初始探索,因此不需要等待,到达新位置只需要花费timePerRoad时间

接下来有两个点可以当成新的源点,根据Dijistra算法,我们可以选择其中较小权重(时间)的点作为源点,此时由于两点权重(时间)相同,因此任选一个都可以

比如我们选择(1,0)点作为新的源点,此时该点可以向下和向右探索:

  • 向下的话,则是直线运动,需要等待,此时需要花费 timePerRod + lights[1][1] 的时间
  • 向右的话,则是左转运动,需要等待,此时需要花费 timePerRod + lights[1][1] 的时间

接下来,需要从124,124,60中选择最小的60作为源点进行探索,该点只能向右和向下探索

  • 向右的话,则是直线运动,需要等待,此时需要花费 timePerRod + lights[0][1] 的时间
  • 向下的话,则是右转运动,不需要等待,此时仅需要花费 timePerRod 时间,即起点(0,0)到达(1,1)沿当前路径只需要120时间,要比之前探索的124小,根据Dijistra算法,更新dist[]

接下来,需要从124,120,122中选择最小的120作为源点进行探索,该点只能向右和向下探索

  • 向右的话,则是左转运动,需要等待,此时需要花费 timePerRod + lights[1][1] 的时间
  • 向下的话,则是直线运动,需要等待,此时需要花费 timePerRod + lights[1][1] 的时间

接下来,需要从124,122,185,185中选择最小的122作为源点进行探索,该点只能向下探索

向右的话,则是右转运动,不需要等待,仅需要花费 timePerRod 时间,此时起点到达(1, 2)位置需要122+60 = 182时间,要比(1,2)当前记录的185时间更短,因此根据Dijistra算法,会更新dist[1][2] = 182,但是此时就会出问题!!!!

我们快速走完后续流程,看下结果:

即按照Dijistra算法的逻辑,最终起点(0,0)到终点(2,2)的最短时间为248,但是这是有问题的,我们回到之前锚定问题的状态:

如果上面绿色箭头选择不更新(1,2)位置的时间的话,即保持dist[1][2] = 185,那么结果如下:

最终起点(0,0)到终点(2,2)的最短时间为245,相较于Dijistra算法得出的最优选择,时间更短。

本题的街道本质上,是一个动态边权的有权图,两个点之间的边权,会因为第三个点的加入而改变,因此边权是动态的。而Dijistra算法无法处理这种情况。

我理解,本题只能进行暴力搜索所有起点到终点的路径,并根据拐弯规则计算动态边权,得出最短时间的路径。

本题的n和m的范围为[1,9],不是很大,因此暴力搜索(如DFS)应该不会超时。

JS算法源码

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

void (async function () {
  const [n, m] = (await readline()).split(" ").map(Number);

  const lights = [];
  for (let i = 0; i < n; i++) {
    lights.push((await readline()).split(" ").map(Number));
  }

  const timePerRoad = parseInt(await readline());

  const [rowStart, colStart] = (await readline()).split(" ").map(Number);
  const [rowEnd, colEnd] = (await readline()).split(" ").map(Number);

  // 记录访问过的点,防止走回路
  const visited = new Array(n).fill(0).map(() => new Array(m).fill(false));

  const offsets = [
    [-1, 0],
    [1, 0],
    [0, -1],
    [0, 1],
  ];

  function getResult() {
    // 初始时起点位置标记为访问过
    visited[rowStart][colStart] = true;

    // 记录起点到终点的最小花费时间,这里minCost定义为数组,是为了其在dfs函数调用结束后,不会被释放内存,因为它是引用类型变量
    const minCost = [Infinity];
    // 开始暴搜所有起点到终点的路径
    dfs(rowStart, colStart, -1, -1, 0, minCost);
    return minCost[0];
  }

  /**
   * 暴力搜索
   *
   * @param curX 当前位置横坐标
   * @param curY 当前位置纵坐标
   * @param preX 上一个位置横坐标
   * @param preY 上一个位置纵坐标
   * @param cost 到达当前位置花费的时间
   * @param minCost 记录起点到终点的最小花费时间
   */
  function dfs(curX, curY, preX, preY, cost, minCost) {
    // 如果到达当前前花费的时间cost 达到了 已知minCost,那么后续路径就没必要探索了,因为必然要比minCost大
    if (cost >= minCost[0]) {
      return;
    }

    // 如果当前点是终点,且花费的时间cost更少,则更新minCost
    if (curX == rowEnd && curY == colEnd) {
      minCost[0] = cost;
      return;
    }

    // 否则,从当前位置的四个方向继续探索路径
    for (let [offsetX, offsetY] of offsets) {
      // 新位置
      const nextX = curX + offsetX;
      const nextY = curY + offsetY;

      // 新位置越界或者已经访问过,则不能访问,继续其他方向探索
      if (
        nextX < 0 ||
        nextX >= n ||
        nextY < 0 ||
        nextY >= m ||
        visited[nextX][nextY]
      )
        continue;

      // 标记新位置访问过
      visited[nextX][nextY] = true;

      // 根据pre,cur,next三点,判断拐弯方向
      const direction = getDirection(preX, preY, curX, curY, nextX, nextY);

      // cur到达next位置必须要增加timePreRoad个时间
      let increment = timePerRoad;
      // preX=-1, preY=-1 表示pre位置不存在,此时探索下一个位置不需要花费等待周期
      if (preX >= 0 && preY >= 0 && direction >= 0) {
        // pre位置存在,且cur->next是左拐或者直行,则需要增加当前位置对应的等待周期时间
        increment += lights[curX][curY];
      }

      // 递归进入新位置
      dfs(nextX, nextY, curX, curY, cost + increment, minCost);

      // 回溯
      visited[nextX][nextY] = false;
    }
  }

  /**
   * 根据三点坐标,确定拐弯方向
   *
   * @param preX 前一个点横坐标
   * @param preY 前一个点纵坐标
   * @param curX 当前点横坐标
   * @param curY 当前点纵坐标
   * @param nextX 下一个点横坐标
   * @param nextY 下一个点纵坐标
   * @return cur到next的拐弯方向, >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
   */
  function getDirection(preX, preY, curX, curY, nextX, nextY) {
    // 向量 pre->cur
    const dx1 = curX - preX;
    const dy1 = curY - preY;

    // 向量 cur->next
    const dx2 = nextX - curX;
    const dy2 = nextY - curY;

    // 两个向量的叉积 >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
    return dx1 * dy2 - dx2 * dy1;
  }

  console.log(getResult());
})();

Java算法源码

import java.util.Scanner;

public class Main {
  static int n;
  static int m;
  static int[][] lights;
  static int timePreRoad;
  static int rowStart;
  static int colStart;
  static int rowEnd;
  static int colEnd;

  static boolean[][] visited;
  static int[][] offsets = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

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

    n = sc.nextInt();
    m = sc.nextInt();

    lights = new int[n][m];
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        lights[i][j] = sc.nextInt();
      }
    }

    timePreRoad = sc.nextInt();

    rowStart = sc.nextInt();
    colStart = sc.nextInt();

    rowEnd = sc.nextInt();
    colEnd = sc.nextInt();

    System.out.println(getResult());
  }

  public static int getResult() {
    // 记录访问过的点,防止走回路
    visited = new boolean[n][m];
    // 初始时起点位置标记为访问过
    visited[rowStart][colStart] = true;

    // 记录起点到终点的最小花费时间,这里minCost定义为数组,是为了其在dfs函数调用结束后,不会被释放内存,因为它是引用类型变量
    int[] minCost = {Integer.MAX_VALUE};
    // 开始暴搜所有起点到终点的路径
    dfs(rowStart, colStart, -1, -1, 0, minCost);
    return minCost[0];
  }

  /**
   * 暴力搜索
   *
   * @param curX 当前位置横坐标
   * @param curY 当前位置纵坐标
   * @param preX 上一个位置横坐标
   * @param preY 上一个位置纵坐标
   * @param cost 到达当前位置花费的时间
   * @param minCost 记录起点到终点的最小花费时间
   */
  public static void dfs(int curX, int curY, int preX, int preY, int cost, int[] minCost) {
    // 如果到达当前前花费的时间cost 达到了 已知minCost,那么后续路径就没必要探索了,因为必然要比minCost大
    if (cost >= minCost[0]) {
      return;
    }

    // 如果当前点是终点,且花费的时间cost更少,则更新minCost
    if (curX == rowEnd && curY == colEnd) {
      minCost[0] = cost;
      return;
    }

    // 否则,从当前位置的四个方向继续探索路径
    for (int[] offset : offsets) {
      // 新位置
      int nextX = curX + offset[0];
      int nextY = curY + offset[1];

      // 新位置越界或者已经访问过,则不能访问,继续其他方向探索
      if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m || visited[nextX][nextY]) continue;

      // 标记新位置访问过
      visited[nextX][nextY] = true;

      // 根据pre,cur,next三点,判断拐弯方向
      int direction = getDirection(preX, preY, curX, curY, nextX, nextY);

      // cur到达next位置必须要增加timePreRoad个时间
      int increment = timePreRoad;
      // preX=-1, preY=-1 表示pre位置不存在,此时探索下一个位置不需要花费等待周期
      if (preX >= 0 && preY >= 0 && direction >= 0) {
        // pre位置存在,且cur->next是左拐或者直行,则需要增加当前位置对应的等待周期时间
        increment += lights[curX][curY];
      }

      // 递归进入新位置
      dfs(nextX, nextY, curX, curY, cost + increment, minCost);

      // 回溯
      visited[nextX][nextY] = false;
    }
  }

  /**
   * 根据三点坐标,确定拐弯方向
   *
   * @param preX 前一个点横坐标
   * @param preY 前一个点纵坐标
   * @param curX 当前点横坐标
   * @param curY 当前点纵坐标
   * @param nextX 下一个点横坐标
   * @param nextY 下一个点纵坐标
   * @return cur到next的拐弯方向, >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
   */
  public static int getDirection(int preX, int preY, int curX, int curY, int nextX, int nextY) {
    // 向量 pre->cur
    int dx1 = curX - preX;
    int dy1 = curY - preY;

    // 向量 cur->next
    int dx2 = nextX - curX;
    int dy2 = nextY - curY;

    // 两个向量的叉积 >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
    return dx1 * dy2 - dx2 * dy1;
  }
}

Python算法源码

import sys

# 输入获取
n, m = map(int, input().split())
lights = [list(map(int, input().split())) for _ in range(n)]
timePerRoad = int(input())
rowStart, colStart = map(int, input().split())
rowEnd, colEnd = map(int, input().split())

offsets = ((-1, 0), (1, 0), (0, -1), (0, 1))
visited = [[False] * m for _ in range(n)]


# 根据三点坐标,确定拐弯方向
def getDirection(preX, preY, curX, curY, nextX, nextY):
    """
    :param preX: 前一个点横坐标
    :param preY: 前一个点纵坐标
    :param curX: 当前点横坐标
    :param curY: 当前点纵坐标
    :param nextX: 下一个点横坐标
    :param nextY: 下一个点纵坐标
    :return: cur到next的拐弯方向, >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
    """
    # 向量 pre->cur
    dx1 = curX - preX
    dy1 = curY - preY

    # 向量 cur->next
    dx2 = nextX - curX
    dy2 = nextY - curY

    #  两个向量的叉积 >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
    return dx1 * dy2 - dx2 * dy1


# 暴力搜索
def dfs(curX, curY, preX, preY, cost, minCost):
    """
    :param curX: 当前位置横坐标
    :param curY: 当前位置纵坐标
    :param preX: 上一个位置横坐标
    :param preY: 上一个位置纵坐标
    :param cost: 到达当前位置花费的时间
    :param minCost: 记录起点到终点的最小花费时间
    """
    # 如果到达当前前花费的时间cost 达到了 已知minCost,那么后续路径就没必要探索了,因为必然要比minCost大
    if cost >= minCost[0]:
        return

    # 如果当前点是终点,且花费的时间cost更少,则更新minCost
    if curX == rowEnd and curY == colEnd:
        minCost[0] = cost
        return

    # 否则,从当前位置的四个方向继续探索路径
    for offsetX, offsetY in offsets:
        # 新位置
        nextX = curX + offsetX
        nextY = curY + offsetY

        # 新位置越界或者已经访问过,则不能访问,继续其他方向探索
        if nextX < 0 or nextX >= n or nextY < 0 or nextY >= m or visited[nextX][nextY]:
            continue

        # 标记新位置访问过
        visited[nextX][nextY] = True

        # 根据pre,cur,next三点,判断拐弯方向
        direction = getDirection(preX, preY, curX, curY, nextX, nextY)

        # cur到达next位置必须要增加timePreRoad个时间
        increment = timePerRoad
        # preX=-1, preY=-1 表示pre位置不存在,此时探索下一个位置不需要花费等待周期
        if preX >= 0 and preY >= 0 and direction >= 0:
            # pre位置存在,且cur->next是左拐或者直行,则需要增加当前位置对应的等待周期时间
            increment += lights[curX][curY]

        # 递归进入新位置
        dfs(nextX, nextY, curX, curY, cost + increment, minCost)

        # 回溯
        visited[nextX][nextY] = False


# 算法入口
def getResult():
    visited[rowStart][colStart] = True

    minCost = [sys.maxsize]
    dfs(rowStart, colStart, -1, -1, 0, minCost)

    return minCost[0]


# 算法调用
print(getResult())

C算法源码

#include <stdio.h>
#include <limits.h>

#define MAX_SIZE 10

int n, m;
int lights[MAX_SIZE][MAX_SIZE];
int timePerRoad;
int rowStart, colStart;
int rowEnd, colEnd;

int offsets[4][2] = {{-1, 0},
                     {1,  0},
                     {0,  -1},
                     {0,  1}};

int visited[MAX_SIZE][MAX_SIZE] = {0};

/*!
 * 根据三点坐标,确定拐弯方向
 * @param preX 前一个点横坐标
 * @param preY 前一个点纵坐标
 * @param curX 当前点横坐标
 * @param curY 当前点纵坐标
 * @param nextX 下一个点横坐标
 * @param nextY 下一个点纵坐标
 * @return cur到next的拐弯方向, >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
 */
int getDirection(int preX, int preY, int curX, int curY, int nextX, int nextY) {
    // 向量 pre->cur
    int dx1 = curX - preX;
    int dy1 = curY - preY;

    // 向量 cur->next
    int dx2 = nextX - curX;
    int dy2 = nextY - curY;

    // 两个向量的叉积 >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
    return dx1 * dy2 - dx2 * dy1;
}

/*!
 * 暴力搜索
 * @param curX 当前位置横坐标
 * @param curY 当前位置纵坐标
 * @param preX 上一个位置横坐标
 * @param preY 上一个位置纵坐标
 * @param cost 到达当前位置花费的时间
 * @param minCost 记录起点到终点的最小花费时间
 */
void dfs(int curX, int curY, int preX, int preY, int cost, int minCost[]) {
    // 如果到达当前前花费的时间cost 达到了 已知minCost,那么后续路径就没必要探索了,因为必然要比minCost大
    if (cost >= minCost[0]) {
        return;
    }

    // 如果当前点是终点,且花费的时间cost更少,则更新minCost
    if (curX == rowEnd && curY == colEnd) {
        minCost[0] = cost;
        return;
    }

    // 否则,从当前位置的四个方向继续探索路径
    for (int i = 0; i < 4; i++) {
        // 新位置
        int nextX = curX + offsets[i][0];
        int nextY = curY + offsets[i][1];

        // 新位置越界或者已经访问过,则不能访问,继续其他方向探索
        if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m || visited[nextX][nextY]) {
            continue;
        }

        // 标记新位置访问过
        visited[nextX][nextY] = 1;

        // 根据pre,cur,next三点,判断拐弯方向
        int direction = getDirection(preX, preY, curX, curY, nextX, nextY);

        // cur到达next位置必须要增加timePreRoad个时间
        int increment = timePerRoad;
        // preX=-1, preY=-1 表示pre位置不存在,此时探索下一个位置不需要花费等待周期
        if (preX >= 0 && preY >= 0 && direction >= 0) {
            // pre位置存在,且cur->next是左拐或者直行,则需要增加当前位置对应的等待周期时间
            increment += lights[curX][curY];
        }

        // 递归进入新位置
        dfs(nextX, nextY, curX, curY, cost + increment, minCost);

        // 回溯
        visited[nextX][nextY] = 0;
    }
}

int getResult() {
    // 记录访问过的点,防止走回路
    // 初始时起点位置标记为访问过
    visited[rowStart][colStart] = 1;

    // 记录起点到终点的最小花费时间,这里minCost定义为数组,是为了其在dfs函数调用结束后,不会被释放内存,因为它是引用类型变量
    int minCost[] = {INT_MAX};
    // 开始暴搜所有起点到终点的路径
    dfs(rowStart, colStart, -1, -1, 0, minCost);
    return minCost[0];
}

int main() {
    scanf("%d %d", &n, &m);

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d", &lights[i][j]);
        }
    }

    scanf("%d", &timePerRoad);

    scanf("%d %d", &rowStart, &colStart);

    scanf("%d %d", &rowEnd, &colEnd);

    printf("%dn", getResult());

    return 0;
}

免责声明:

1、IT资源小站为非营利性网站,全站所有资料仅供网友个人学习使用,禁止商用
2、本站所有文档、视频、书籍等资料均由网友分享,本站只负责收集不承担任何技术及版权问题
3、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除下载链接并致以最深的歉意
4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
5、一经注册为本站会员,一律视为同意网站规定,本站管理员及版主有权禁止违规用户
6、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和IT资源小站的同意
7、IT资源小站管理员和版主有权不事先通知发贴者而删除本文

0

评论0

站点公告

没有账号?注册  忘记密码?