端盘子问题(二分查找+广度优先)

题目描述

终于中午放学铃声打响了,小明想要尽快地前往食堂打自己最喜欢吃的菜。小明觉得能打到喜欢吃的菜越多越好,他会准备好若干个盘子再出发,带了几个盘子就打几份菜。但是一路上人群拥挤程度不同,如果太过拥挤,小明会拿不稳太多盘子。

经过观察,小明发现食堂的地形可以用一个 n × n 的二维平面表示:

  • 初始位置(1,1)
  • 食堂的位置(n,n)
  • 在除开初始位置和食堂位置外的所有格子上,都有一个最大允许的盘子数量,表示小明在经过该格子时最多能带的盘子数量。如果小明带的盘子数量超过这个限制,会被人群挤掉,前功尽弃。

小明只能在二维平面内行走,并且每次可以花费 1 秒,从当前位置 (i, j) 移动到相邻的 (i+1, j)(i, j+1)(i-1, j)(i, j-1)

你需要帮助小明找到:

  • 在 T 秒内从起点 (1,1) 走到终点 (n,n) 时,最多能带多少个盘子。

如果无论如何也不能在规定时间内到达终点,输出 0


输入描述

第一行包含两个整数 nT,分别表示:

  • n:二维平面的大小为 n × n
  • T:时间限制(秒数)

接下来 n 行,每行包含 n 个整数,描述每个位置的最大允许盘子数量:

  • 如果某位置的值为 -1,表示没有限制(即可以带任意数量的盘子)。
  • 起点 (1,1)终点 (n,n) 的值为 -1
  • 其他格子的值为 >= 0,表示该位置的最大允许盘子数量。

输入约束:

  • 1≤n≤10001≤n≤1000
  • 1≤T≤n21≤Tn2
  • −1≤ai,j≤1,000,000−1≤a**i,j≤1,000,000(仅 (1,1)(n,n) 的值为 -1

输出描述

输出一行,表示小明最多能带的盘子数量
如果无论如何也不能在规定时间内到达终点,输出 0

样例输入

4 6
-1 2 1 1
0 2 2 0
0 0 2 2
1 1 0 -1

样例输出

2

解释

在样例输入中,小明需要在 6 秒内从 (1,1) 走到 (4,4)
经过仔细计算,最多可以携带 2 个盘子,否则某些位置的限制无法满足。

提示

  1. 二分查找+广度优先搜索(BFS)

    是解决这个问题的有效方法:

    • 使用 二分查找 来找到可以带的最大盘子数量。
    • 每次假设一个盘子数量,用 BFS 检查是否能在规定时间内从起点走到终点。
  2. 时间复杂度优化:由于 n 最大可以达到 1000,路径搜索需要高效的算法,所以 BFS 是更合适的选择。

解题:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    // 四个方向的移动向量,分别表示:向下,向上,向右,向左
    static int[] dx = {1, -1, 0, 0};
    static int[] dy = {0, 0, 1, -1};
    
    // 定义全局变量n(表示二维平面的大小)和T(表示时间限制)
    static int n, T;
    
    // 定义二维数组grid,用来存储每个格子允许的最大盘子数量
    static int[][] grid;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 读取n和T
        n = sc.nextInt();
        T = sc.nextInt();
        grid = new int[n][n];

        // 读取地图数据,即每个格子允许的最大盘子数量
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 进行二分查找,查找能带的最大盘子数量
        int left = 0;       // 二分查找的左边界,最小值是0
        int right = 1000000; // 二分查找的右边界,最大值为1000000(题目允许的最大值)
        int result = 0;     // 用来记录最终能带的最大盘子数量

        // 开始二分查找
        while (left <= right) {
            // 取当前的中间值mid,表示我们假设小明能带mid个盘子
            int mid = (left + right) / 2;
            
            // 调用canReach函数判断:带mid个盘子能否在规定的时间T内到达终点
            if (canReach(mid)) {
                // 如果可以到达,说明mid是一个可行的解,记录结果并尝试更大的值
                result = mid;
                left = mid + 1;  // 尝试更大的盘子数量
            } else {
                // 如果不能到达,说明mid过大,需要尝试更小的值
                right = mid - 1;
            }
        }

        // 输出最后找到的最大盘子数量
        System.out.println(result);
    }

    /**
     * 判断是否可以带至少k个盘子,在不超过T秒的情况下从(1,1)到达(n,n)
     * @param k: 假设要带的盘子数量
     * @return: 是否可以在T秒内到达终点
     */
    private static boolean canReach(int k) {
        // 使用队列进行广度优先搜索(BFS)
        Queue<int[]> queue = new LinkedList<>();
        // 用一个二维数组visited来标记某个位置是否被访问过,防止重复搜索
        boolean[][] visited = new boolean[n][n];
        
        // 起点是(0, 0),即(1,1)的位置,初始时间为0
        queue.offer(new int[]{0, 0, 0});
        visited[0][0] = true;  // 标记起点已经访问过

        // 开始BFS搜索
        while (!queue.isEmpty()) {
            int[] current = queue.poll();  // 取出队列中的当前点
            int x = current[0], y = current[1], time = current[2]; // 当前的x, y坐标和已消耗的时间

            // 如果已经超过时间限制,继续检查下一个可能的位置
            if (time > T) {
                continue;
            }

            // 如果已经到达终点(n-1, n-1),返回true,表示带k个盘子可以成功到达
            if (x == n - 1 && y == n - 1) {
                return true;
            }

            // 向四个方向移动,分别是:下、上、右、左
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i];  // 新的x坐标
                int ny = y + dy[i];  // 新的y坐标

                // 检查新位置是否在二维平面内,并且没有访问过
                if (nx >= 0 && nx < n && ny >= 0 && ny < n && !visited[nx][ny]) {
                    // 检查新位置是否允许带k个盘子,起点和终点没有限制(值为-1)
                    if (grid[nx][ny] == -1 || grid[nx][ny] >= k) {
                        // 标记新位置为已访问,并加入队列继续搜索
                        visited[nx][ny] = true;
                        queue.offer(new int[]{nx, ny, time + 1});  // 时间加1
                    }
                }
            }
        }

        // 如果搜索结束还没能到达终点,返回false
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值