【蓝桥杯真题】2026/4/7【前缀和】

image

image

需要注意的点:
1.步数的意思是什么
2.起点从0开始
3.矿洞矿的数量可以不一样
利用前缀和+二分


import java.util.*;

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

        int[] nums = new int[n];
        //用来存每个矿洞里有多少个矿,比如 输入 -3 -3 则代表-3矿洞有两个矿,用于计算出前缀和
        HashMap<Integer, Integer> map = new HashMap<>();
        //这一步开始预处理map,保存每个矿洞有多少个矿
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }

        //排序,因为题意表示是从0开始可以走m步
        Arrays.sort(nums);
        // 去重,保证计算前缀和时,不会重复添加矿石
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (i == 0 || nums[i] != nums[i - 1]) {
                list.add(nums[i]);
            }
        }
        // 强制添加0,保证起点存在(解决无0崩溃)
        if (!list.contains(0)) {
            list.add(0);
            Collections.sort(list); // 重新排序
        }

        int size = list.size();
        long[] pre = new long[size + 1];
        //pos0表示0所在数组的位置
        int pos0 = -1;
        for (int i = 0; i < size; i++) {
            //pre[i]代表第i个数,pre[1]计算的就是一个数的前缀和nums[0]
            pre[i + 1] = pre[i] + map.getOrDefault(list.get(i), 0);
            if (list.get(i) == 0) pos0 = i;
        }

        long maxAns = 0;
        // 1. 只向右走,注意步数到底是什么,比如list.get(2)=3,意味着要走三步
        for (int i = pos0; i < size; i++) {
            if (list.get(i) > m) break;
            maxAns = Math.max(maxAns, pre[i + 1] - pre[pos0]);
        }
        // 2. 只向左走,注意步数到底是什么,比如list.get(2)=-4,意味着要走四步
        for (int i = pos0; i >= 0; i--) {
            if (Math.abs(list.get(i)) > m) break;
            maxAns = Math.max(maxAns, pre[pos0 + 1] - pre[i]);
        }

        // 3. 先左 → 折返 → 右(修复二分边界)
        for (int i = 0; i < pos0; i++) {
            int cost = 2 * Math.abs(list.get(i));
            if (cost > m) continue;
            int remain = m - cost;
            int l = pos0, r = size - 1, right = pos0;
            while (l <= r) {
                int mid = (l + r) / 2;
                if (list.get(mid) <= remain) {
                    right = mid;
                    l = mid + 1;
                } else r = mid - 1;
            }
            maxAns = Math.max(maxAns, pre[right + 1] - pre[i]);
        }

        // 4. 先右 → 折返 → 左
        for (int i = pos0 + 1; i < size; i++) {
            int cost = 2 * list.get(i);
            if (cost > m) break;
            int remain = m - cost;
            int l = 0, r = pos0, left = pos0;
            while (l <= r) {
                int mid = (l + r) / 2;
                // 修复:这里之前逻辑颠倒,现在精准找最远左边界
                if (Math.abs(list.get(mid)) <= remain) {
                    left = mid;
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
            maxAns = Math.max(maxAns, pre[i + 1] - pre[left]);
        }

        System.out.println(maxAns);
    }
}
posted @ 2026-04-07 18:25  Jwwind  阅读(23)  评论(0)    收藏  举报