传送门:https://www.luogu.com.cn/problem/P4447
有一些贪心题目是可以通过直接观察例子得出贪心策略,即拿贪心策略去推断代码。我认为这个题目应该是由代码推断贪心策略,即先想想如果用代码怎么处理这样的问题,从而得出贪心策略,其实也就是得出写代码的思路。
刚拿到这个题目无从下手,就从代码的角度考虑一下。题目是分组,而且小组可能有多个,那么对于每个小组我是不是应该要有一个数组来存储它?假设现在已经有五个小组,我用五个数组存储它小组里面的值,那当我在遍历所有成员的能力值(已经排序完成的)时,对于某一个成员,我要给他分组,如果他的能力刚好就是某个小组的下一个应该填的值,那我是不是应该把这个成员放进这个小组里?那万一遇到有两个或者多个小组都满足条件呢?为了满足题意我应该把这个成员放在小组人数最少的组里。那万一没有小组符合条件怎么说?那只能多加一个小组,从五个小组变成六个小组,然后把该成员放进第六个小组里面了。
由此我们的贪心策略就出来了,遍历每个成员能力值,如果有小组刚好可以放置,那我就在满足条件的小组里面选人数最少的小组,把该成员放进去,如果没有小组满足,就新开一个小组,把该成员放在新小组里面。
现在开始考虑代码书写,对于每一个小队都开一个数组这样的想法并不现实,由于小队内的数字都是连续的(题目要求),那其实我只要存储每个小队最后一个数字我就可以知道它的下一个数字应该是多少了,因此只需要一个数组来表示各个小队,用q[i]表示第i个小队,而每个小队的长度也需要存储,所以还要再多一个数组来存储长度。
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX = 1e5 + 50;
int Array[MAX], Exp[MAX], Length[MAX];
//分别存储能力值,各小组期待值,各小组长度
//假如一个小组的最后一个数是5,那么该小组的期待值就是6
int main(void)
{
int n = 0, top = 1, ans = MAX;
//ans表示答案,top表示现有小组个数,初始化为1
cin >> n;
for (int i = 1; i <= n; i++)
cin >> Array[i];
sort(Array + 1, Array + n + 1);//从小到大排序
Exp[top] = Array[1] + 1; Length[top] = 1;
//把第一个小组初始化了,省得下面特判
for (int i = 2; i <= n; i++)
{
int pos = upper_bound(Exp + 1, Exp + top + 1, Array[i]) - Exp;
//这里用了upper__bound函数来找出有可能满足条件的小队位置
if (Exp[pos - 1] == Array[i]) {
Exp[pos - 1]++; Length[pos - 1]++;
}
//如果恰好满足条件就直接存放
else {
Exp[++top] = Array[i] + 1;
Length[top] = 1;
}
//不满足就新加一个小队
/*
* 这里的查找满足条件并且人数最少的小队可以用
* 二分,也可以直接暴力枚举。如果不能理解为什么
* upper__bound函数可以满足小队人数最少建议不要使用
* 暴力枚举的代码更好理解
* 当然也建议多多思考,多举几个例子就能发现
* upper__bound能满足题意的原因,多思考才能进步
*/
}
for (int i = 1; i <= top; i++)
if (Length[i] < ans)
ans = Length[i];//找最大值
cout << ans;
return 0;//完结撒花
}