P4447 AHOI2018初中组 分组 题解复盘
模块:贪心算法
目标:将所有队员分成若干组,使人数最少的组人数最大
基本信息
| 项目 | 内容 |
|---|---|
| 题目编号、来源 | P4447 洛谷 / AHOI2018初中组 分组 |
| 训练层级 | B 贪心进阶 |
| 知识版块 | 贪心、排序、优先队列、状态维护 |
解题前・关键信号识别
| 维度 | 分析 |
|---|---|
| 目标、约束、底层结构 | 目标 :将所有队员分成若干组,使最小组的人数尽可能多。 约束 :每组实力值必须连续,并且同一个实力值不能出现在同一组。 底层结构:按照实力值排序后,从小到大处理每个数字,将当前数字加入满足条件的最短组,使各组长度尽可能均衡。 |
| 数据规模 | n≤100000,需要 O(nlogn) 算法。排序和优先队列可以满足要求。 |
| 候选算法和依据 | 算法:排序 + 贪心 + 优先队列。 依据:处理实力值 x 时,只能加入最后一个数字为 x-1 的组;为了最大化最小组长度,应优先扩展长度最短的组。 |
| 复杂度预判 | 排序 O(nlogn),每个元素最多进行一次堆操作 O(logn)。 时间复杂度 O(nlogn),空间复杂度 O(n)。 |
解题后・外化复盘
| 维度 | 内容 |
|---|---|
| 实现结构 / 核心思路 | 第一步对所有实力值排序。第二步使用 map 保存不同结尾数字对应的所有组长度,其中优先队列维护当前最短的组。第三步遍历每个实力值 x:如果存在以 x-1 结尾的组,则取出其中长度最短的组,将 x 加入,长度增加1;否则新建一个长度为1的组。最后统计所有组长度中的最小值,即为答案。 |
| 错因回溯 | 1. 一开始容易认为应该优先扩展最长的组,但题目目标是让最短组最大,因此应该补短组。 2. 没有理解 map + priority_queue 的作用,需要维护不同结尾数字的组状态。 3. 使用堆时容易混淆 top() 和 pop(),top() 查看元素,pop() 删除元素。 |
| 边界和易错点 | 1. 实力值范围很大,不能直接开数组,需要使用 map。 2. 相同实力值不能放在同一个组,所以需要创建不同的组。 3. 当前数字只能加入以 x-1 结尾的组。 4. 扩展一个组后,需要删除旧长度,再加入新的长度。 |
| 下次看到什么信号,我应该想到这个方法 | 看到「分组 + 连续要求 + 最大化最小组长度」,想到:排序后维护多个组状态,用优先队列贪心分配。 |
AC 完整代码
cpp
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<queue>
using namespace std;
int main()
{
int n;
cin>>n;
vector<long long> a(n);
for(int i=0;i<n;i++)
cin>>a[i];
sort(a.begin(),a.end());
map<long long,priority_queue<int,vector<int>,greater<int>>> mp;
for(auto x:a)
{
if(!mp[x-1].empty())
{
int len=mp[x-1].top();
mp[x-1].pop();
mp[x].push(len+1);
}
else
{
mp[x].push(1);
}
}
int ans=1e9;
for(auto &p:mp)
{
while(!p.second.empty())
{
ans=min(ans,p.second.top());
p.second.pop();
}
}
cout<<ans;
return 0;
}