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 ✅

相关推荐
小蒜学长1 小时前
基于SSM+VUE的电影售票平台的设计与实现(代码+数据库+LW)
java·vue.js·spring boot·后端·ssm框架·电影售票平台
朝朝辞暮i1 小时前
C++ 第 36 课:Action——机械臂/VLA 非常重要
开发语言·c++·算法·ros2
Wang's Blog1 小时前
Java框架 SpringCloud 快速入门: Feign 替代 RestTemplate 实现声明式远程调用
java·开发语言·spring cloud
谢亮_vipxieliang1 小时前
Go 结构体与方法集核心知识点
java·开发语言·golang
ra90fy1 小时前
B3873 [GESP202309 六级] 小杨买饮料
c++·算法
EatFan1 小时前
2026 Java 后端面试风向变了:八股文只是门槛,场景化追问才是淘汰线
java·开发语言·jvm·面试·后端面试·八股文·full gc
Omics Pro1 小时前
北理工李荣华:AI虚拟细胞证据多模态推理基准
数据库·人工智能·算法·机器学习·自然语言处理
Q26433650231 小时前
【有源码】基于Spring Boot+Vue的骑行爱好者平台的设计与实现-计算机毕设选题推荐
java·vue.js·spring boot·毕业设计·课程设计·数据可视化·源代码
xieliyu.1 小时前
LeetCode 1419 数青蛙:状态模拟求最少青蛙数量|Java 题解
java·算法·leetcode