路口最短时间问题 OD

题目描述

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

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

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

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

其中:

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

输入描述

第一行输入 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

public class zuiduanshijian {
    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());
    }

    private 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 记录起点到终点的最小花费时间
     */
    private 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;
    }
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值