目录
[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 基本流程
-
先把
n名学生和m组读入 -
每名学生放入
map表中(key = 声部编号,value = 该声部人数) -
找出
map表中最多的人数hmax -
判断无解: 如果
map.size() > m,说明声部种类数超过了组数,绝对不能分成m组,输出-1 -
从
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 ✅
