NC229023 题解:二分答案求解“最大化最小值“问题

目录

​编辑

一、题目描述

二、普通解题思路

[2.1 基本流程](#2.1 基本流程)

[2.2 check 函数的判断条件](#2.2 check 函数的判断条件)

[2.3 为什么是 <= m 而不是 == m?](#2.3 为什么是 <= m 而不是 == m?)

[2.4 普通解法代码](#2.4 普通解法代码)

三、二分解题思路

[3.1 为什么可以用二分?](#3.1 为什么可以用二分?)

[3.2 二分边界与收缩逻辑](#3.2 二分边界与收缩逻辑)

[3.3 二分模板](#3.3 二分模板)

[3.4 二分解法完整代码](#3.4 二分解法完整代码)

四、示例模拟


题目链接: 分组

一、题目描述

dd 当上了宣传委员,开始组织迎新晚会。已知班里有 n 个同学,每个同学有且仅有一个擅长的声部。要把同学们分成恰好 m 组,为了不搞砸节目,每一组里的同学都必须擅长同一个声部(不同组可以是同一声部)。但她不希望出现任何一组的人过多,否则可能导致场地分配不协调。

也就是说,她希望人数最多的小组的人尽可能少 。如果无法顺利安排,请输出 -1。

输入格式:

  • 第一行两个数 n, m(1 ≤ m ≤ n ≤ 100000)

  • 接下来一行 n 个数 a[i],表示第 i 个学生擅长的声部

输出格式:

  • 输出一个数,表示人数最多的小组的人数;无法安排输出 -1

示例:

复制代码
输入:
5 3
2 2 3 3 3

输出:
2

二、普通解题思路

2.1 基本流程

  1. 先把 n 名学生和 m 组读入

  2. 每名学生放入 map 表中(key = 声部编号,value = 该声部人数)

  3. 找出 map 表中最多的人数 hmax

  4. 判断无解: 如果 map.size() > m,说明声部种类数超过了组数,绝对不能分成 m 组,输出 -1

  5. 从 1 到 hmax 枚举每组最多人数 i,用 check(i) 判断是否可行

2.2 check 函数的判断条件

java 复制代码
public static boolean check(int x) {
    int g = 0;
    for(int a : map.values()) {
        g += a / x + (a % x == 0 ? 0 : 1);  // 向上取整 ceil(a/x)
    }
    return g <= m;
}

2.3 为什么是 <= m 而不是 == m?

我们选取好的每组最多人数 x,在 check 函数中,当 g < m 时,我们可以把其他组的人数再分出来一些。因为我们要找的是最大值的最小值 ,这样分下来的组数中,人数最多的人也是最少的,所以此处用 <= m。

核心原因: 组数只能"往上拆"(一个声部可拆成更多组),不能"往下合"。所以最少组数 ≤ m 时,总能拆到恰好 m 组;反之则做不到。

情况 最少组数 g 能否凑成恰好 m 组
g < m 有富余 ✅ 拆分补足
g == m 刚好 ✅ 不用拆
g > m 超了 ❌ 无法减少

2.4 普通解法代码

java 复制代码
import java.util.*;

public class Main {
    
    public static int m, n;
    public static Map<Integer, Integer> map = new HashMap<>();
    
    public static boolean check(int x) {
        int g = 0;
        for(int a : map.values()) {
            g += a / x + (a % x == 0 ? 0 : 1);
        }
        return g <= m;
    }
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        
        int hmax = 0;
        for(int i = 0; i < n; i++) {
            int x = in.nextInt();
            map.put(x, map.getOrDefault(x, 0) + 1);
            hmax = Math.max(hmax, map.get(x));
        }
        
        if(map.size() > m) {
            System.out.println("-1");
        } else {
            for(int i = 1; i <= hmax; i++) {
                if(check(i)) {
                    System.out.println(i);
                    break;
                }
            }
        }
    }
}

三、二分解题思路

3.1 为什么可以用二分?

我们在找"组数中最大值"的时候,是从小到大来找的,具有单调性:

  • 若容量 x 可行,那么更大的 x 一定也可行

  • 若容量 x 不可行,那么更小的 x 一定不可行

这时候就可以使用二分查找来优化。

3.2 二分边界与收缩逻辑

  • 定义 left = 1, right = hmax

  • 当 check(mid) 为 true 时,说明此时的 mid(每组最多人数)可以组成 m 组,那么我们可以让 mid 变小 看看是否还能符合题意 → right = mid

  • 当 check(mid) 为 false 时,说明组成不了 m 组,那么就让 left = mid + 1,尝试每组上限人数增多 是否能够组成 m 组

3.3 二分模板

java 复制代码
int left = 1, right = hmax;
while(left < right) {
    int mid = (left + right) / 2;
    if(check(mid)) right = mid;      // mid 可行,尝试更小
    else left = mid + 1;             // mid 不可行,必须增大
}
System.out.println(left);

3.4 二分解法完整代码

java 复制代码
import java.util.*;

public class Main {
    
    public static int m, n;
    public static Map<Integer, Integer> map = new HashMap<>();
    
    public static boolean check(int x) {
        int g = 0;
        for(int a : map.values()) {
            g += a / x + (a % x == 0 ? 0 : 1);
        }
        return g <= m;
    }
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        
        int hmax = 0;
        for(int i = 0; i < n; i++) {
            int x = in.nextInt();
            map.put(x, map.getOrDefault(x, 0) + 1);
            hmax = Math.max(hmax, map.get(x));
        }
        
        if(map.size() > m) {
            System.out.println("-1");
        } else {
            int left = 1, right = hmax;
            while(left < right) {
                int mid = (left + right) / 2;
                if(check(mid)) right = mid;
                else left = mid + 1;
            }
            System.out.println(left);
        }
    }
}

四、示例模拟

输入:

java 复制代码
5 3
2 2 3 3 3

处理过程:

  • 声部 2:2 人

  • 声部 3:3 人

  • 声部种类数 k = 2 ≤ m = 3,不输出 -1

  • hmax = 3

二分过程:

left right mid check(mid) 计算 结果 操作
1 3 2 ceil(2/2)+ceil(3/2)=1+2=3 ≤ 3 ✅ right = 2
1 2 1 ceil(2/1)+ceil(3/1)=2+3=5 > 3 ❌ left = 2
2 2 - 结束 - 输出 2

输出: 2 ✅

相关推荐
拓恒技术17 小时前
无人机巡检AI算法的工程化落地:从需求到交付的5个核心环节
人工智能·算法·无人机
晨曦中的暮雨18 小时前
限流算法——固定窗口、滑动窗口、漏桶、令牌桶
java·开发语言·算法
怕浪猫18 小时前
Claude Code vs Codex:Computer Use 实现原理深度拆解
算法·面试·github
文丑颜不良啊19 小时前
LeetCode876~900题目解析
java·leetcode
罗西的思考20 小时前
AI 时代的 “房谋杜断”:如何培养能力、品味与判断力
人工智能·算法·机器学习
aloha_78920 小时前
Java 基础、集合、异常与 JUnit 5 速查
java·junit·sqlserver
淡海水20 小时前
07-02-源码-.NET Core GC分配路径
算法·unity·c#·.netcore·gc·1024程序员节
知守观20 小时前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式
独隅20 小时前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code
Wang's Blog21 小时前
Java 项目实战: 交友项目 - 圈子功能实现:总结
java·开发语言·交友