洛谷 P4447 [AHOI2018 初中组] 分组 题解
蒟蒻的思路
这题是个贪心。于是手玩几个样例,看看能找到什么很有用的性质。我们先约定实力值为 \(l\sim r\) 的小组记作 \(G_r^l~(l\le r)\)。
对于这组样例:
7
1 2 2 3 3 4 5
我们以能力值为横轴画出柱状图。
如果划分成 \(G_5^1\) 和 \(G_3^2\),人最少的组有 2 人。

而分成 \(G_3^1\) 和 \(G_5^2\),的情况更优。因为这样分人最少的组有 3 人。

于是我们可以知道,对于任意 \(G_j^i,G_l^k~(i\le k,j\ge l)\),重组为 \(G_k^i,G_l^j\) 一定不会更劣。即最优解中小组两两不包含,\(l\) 更小的组,\(r\) 也一定更小。(证明很容易,可以自己画个图感受一下,我就不写了哈)
于是我们用队列维护每组的人数,依次处理图中“每一列”。可知队列中每一组的 \(l\) 和 \(r\) 都是单调不降的。
- 若这一列比队列长度大,则入队(添加新小组);
- 若这一列比队列长度低,则出队(旧的小组结束);
无论何种情况,最后全队列都要 \(+1\),用懒标记 shift 维护即可。
由于最多有 \(n\) 组,每个小组只进出队列一次,所以时间复杂度为 \(O(n)\),而排序 \(O(n \log n)\),是瓶颈。跑得飞快,卡卡常就到了 72ms,目前全谷最优解。
蒟蒻码
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define int unsigned int // 卡常
#define N 100000
#define INF 0x7f7f7f7f
using namespace std;
char buf[1<<21],*p=buf;
int a[N],q[N],hd,tl,len,cur,cnt,shift,ans=INF;
inline void read(int &x){
int c;
while((c=*p++)<'0'||c>'9');
for(x=c^48;(c=*p++)>='0'&&c<='9';x=(x<<3)+(x<<1)+(c^48));
}
inline void work(int x){ // x为下一列的位置
if(x-cur>1){ // 中间某列高度为 0,即数字不连续
if(!len){ // 队列空,即上一次刚刚全部出队,出现“孤列”
putchar('1'),exit(0);
}
while(len){ // 全部出队,更新ans
ans=min(ans,q[hd++]+shift);
--len;
}
}
else{
if(cnt>len) // 情况一
for(int j=len;j<cnt;++j)
q[tl++]=1-shift; // 入队的数实际为1
else if(cnt<len) // 情况二
for(int j=cnt;j<len;++j)
ans=min(ans,q[hd++]+shift);
len=cnt,++shift; // 整体加一
}
}
signed main(){
int n;
fread(buf,1,1<<21,stdin);
read(n);
if(n==1){
putchar('1');
return 0;
}
for(int i=0;i<n;++i) read(a[i]);
sort(a,a+n),cur=a[0],cnt=1;
for(int i=1;i<n;++i) // 统计每个数字出现次数,即每列高度
if(a[i]==cur) ++cnt;
else work(a[i]),cur=a[i],cnt=1;
work(INF); // 最后一列
printf("%d",ans);
return 0;
}
Update 2025.2.11:修正时间复杂度分析。
Update 2025.6.15:微调图片与文字间距。
Update 2025.6.16:调整公式上下标。

浙公网安备 33010602011771号