「题解」P4447 [AHOI2018初中组]分组
也许是一种奇妙的思路?
我们用类似条形统计图的方式,在数轴上统计各个实力值出现的次数。以样例为例:
题目中的“分组”,就可以理解为在方格中画线——被同一条线相连的方格所对应的同学被分为一组。如:
再演示一个稍微复杂一点的图(删去了数轴):
也许这样不是特别直观?我们规定,删除被画过线的方格,且总是在最下方一行画线:
为方便表述,定义一条线所连接的方格数为这条线的长度,某一列当前的方块数为这一列的高度。
至此,“分组”问题被转化成了一个“俄罗斯方块”式的问题。接下来,我们要研究如何使人数最少的组别人数最大——也就是如何使长度最短的线长度最大。
不妨令每一次画线都从最左边一列开始。
每次都画到底,可以吗?
显然,大多数情况下这不是最优解。最后可能会剩下一个方块“一枝独秀”:
出现这种情况的根本原因是什么?我们发现,“一枝独秀”的方块总是出现在高度较高的几列。
如何解决?我们需要改变画线的方式:
如果右边一列的高度不低于当前列,则连接右边一列最下方的方块。反之,停止画线。
这样,最靠左的一个“峰”相较其右边一列的高度差就不断减小,直到相同。如此反复。记录所画所有线的最短长度,即为答案。
可以用 std::map 关键字自动排序的特性来记录图形。
时间复杂度
参考代码:
#include<cstdio>
#include<map>
std::map<int,int>m;
typedef std::map<int,int>::iterator it;
int main(){
int n,ans=0x3f3f3f3f;
scanf("%d",&n);
for(int i=0;i<n;++i){
int t;
scanf("%d",&t);
++m[t];
//记录图像。
}
while(!m.empty()){
it i=m.begin(),j=m.begin();
--(*i).second;
int t=1;
for(++j;j!=m.end()&&(*j).first==(*i).first+1&&(*j).second>(*i).second;++i,++j){
++t;
--(*j).second;
}
//若 i,j 所对应的能力值是连续的,且 i 对应的那一列高度不高于 j,则继续画线。
i=m.begin();
while(i!=m.end()&&(*i).second==0){
m.erase((*i++).first);
}
//高度降为 0 后直接删除,便于计算。
if(t<ans){
ans=t;
}
//记录答案。
}
printf("%d",ans);
return 0;
}