题目链接:https://www.luogu.com.cn/problem/P4447
很容易想到排序后离散化。
比如: -1 0 0 1 1 2
这样的序列可以离散化为:
编号:1 2 3 4
ai: -1 0 1 2
cs: 1 2 2 1
ai记录的是它原来的能力值,cs对应的是每个能力值的人数。
首先,可以知道的是,只有连续的一串能力值能被分在一组,所以如果出现 1 2 3 6 7 这样的,很容易知道需要将123和67分开处理。
那么就是要解决,如果是连续的能力值,如何使分组人数最少的那组人数最多。
很容易有一种感受是,我们希望一组尽量长,但是又不能太长。
拿上面的例子举例 -1 0 0 1 1 2,如果最长那就是-1 0 1 2,但如果这样选了之后,0 1就被剩下了,剩下的0 1就是人数最少的组,最终答案是2。但如果我们稍微选短一点-1 0 1剩下的就是 0 1 2,答案就是3.
映射到cs数组上就是维护一个不下降序列,第一次是1 2 2,长度为3,在将选的人拿出以后剩下0 1 1 1,第二个不下降序列就是1 1 1(因为是维护不下降序列,出现cs为零一定是从左往右以此出现的,不可能出现1 0 1 1 1这种情况,所以可以删去前导零),长度为3,选完后,再将选出的人删去,最终,所有人均被选出,答案为3.
可以看出排好序后后面找答案的时间段是O(n)的,因为每个能力值只会被删一次。
关于代码:
1.代码是用递归实现的,实际上可以用while。
2.代码序号是从0开始的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, n1;
int ai[100005], cs[100005];
int ans;
void solve(int l, int r) {
int ll = l;//ll的作用是去掉前导零
cs[l]--;
if (!cs[l])
ll = l + 1;
int mini = 1;
for (int i = l + 1; i <= r; ++i) {
if (cs[i] >= cs[i - 1] + 1) {
cs[i]--;
if (!cs[i])
ll = i + 1;
mini++;
} else
break;
}//维护一个不下降的序列,同时处理了cs数组
ans = min(ans, mini);
if (ll == r + 1) //这是所有能力值均被分组的情况
return;
else
solve(ll, r);
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", ai + i);
sort(ai, ai + n);
n1 = 0;
cs[0] = 1;
for (int i = 1; i < n; ++i) {
if (ai[i] == ai[n1])
++cs[n1];
else
ai[++n1] = ai[i], cs[n1] = 1;
}//离散化
ans = n1 + 1;
int l = 0;
for (int i = 1; i <= n1; ++i) {
if (ai[i] != ai[i - 1] + 1) {
solve(l, i - 1); //连续的能力值
l = i;
}
}
solve(l, n1);
printf("%d\n", ans);
return 0;
}