P4447 分组

题目描述

小可可的学校信息组总共有n 个队员,每个人都有一个实力值a[i]。现在,一年一度的编程大赛就要到了,小可可的学校获得了若干个参赛名额,教练决定把学校信息组的nnn 个队员分成若干个小组去参加这场比赛。

但是每个队员都不会愿意与实力跟自己过于悬殊的队员组队,于是要求分成的每个小组的队员实力值连续,同时,一个队不需要两个实力相同的选手。举个例子: [ 1 , 2 , 3 , 4 , 5 ] [ 1 , 2 , 3 , 4 , 5 ] [ 1 , 2 , 3 , 4 , 5 ] [1,2,3,4,5][1, 2, 3, 4, 5][1,2,3,4,5] [1,2,3,4,5][1,2,3,4,5][1,2,3,4,5]是合法的分组方案,因为实力值连续; [ 1 , 2 , 3 , 5 ] [ 1 , 2 , 3 , 5 ] [ 1 , 2 , 3 , 5 ] [1,2,3,5][1, 2, 3, 5][1,2,3,5] [1,2,3,5][1,2,3,5][1,2,3,5]不是合法的分组方案,因为实力值不连续; [ 0 , 1 , 1 , 2 ] [ 0 , 1 , 1 , 2 ] [ 0 , 1 , 1 , 2 ] [0,1,1,2][0, 1, 1, 2][0,1,1,2] [0,1,1,2][0,1,1,2][0,1,1,2]同样不是合法的分组方案,因为出现了两个实力值为1 的选手。

如果有小组内人数太少,就会因为时间不够而无法获得高分,于是小可可想让你给出一个合法的分组方案,满足所有人都恰好分到一个小组,使得人数最少的组人数最多,输出人数最少的组人数的最大值

注意:实力值可能是负数,分组的数量没有限制。
输入格式

输入有两行:
第一行一个正整数n,表示队员数量。
第二行有n 个整数,第i 个整数a[i]表示第i 个队员的实力。
输出格式

输出一行,包括一个正整数,表示人数最少的组的人数最大值。
输入输出样例

输入 #1

7
4 5 2 3 -4 -3 -5

输出 #1

3

说明/提示

【样例解释】

分为2 组,分别为 2 , 3 , 4 , 5 2,3,4,5 2,3,4,5 − 5 , − 4 , − 3 −5,−4,−3 5,4,3,其中最小的组人数为3,可以发现没有比3 更优的分法了。

【数据范围】

数据满足: 1 ≤ n ≤ 100000 , ∣ a [ i ] ∣ ≤ 1 0 9 1≤n≤100000,∣a[i]∣≤10^9 1n100000,a[i]109

解法一 贪心

证明

假设数据为 [ 1 , 2 , 2 , 3 , 3 , 3 , 4 , 6 , 7 ] [1,2,2,3,3,3,4,6,7] [1,2,2,3,3,3,4,6,7]
统计每个数字出现的次数,画成柱状图如下。
柱状图每次选择一组就像玩俄罗斯方块一样把方格消去。
假设 1 , 2 , 3 1,2,3 123为一组,消去后如下。
消去后下面证明一下当柱状图递增时,最少组人数就是最上层的长度
还是拿上面的数据作为例子,只考虑数字 1 , 2 , 3 1,2,3 123柱状图是递增的。
2的个数为2个,3的个数为3个,也就是说不管怎么分组,总有一个3配不到2。最优也就是从3开始往右分组。类似下图,很显然最少组人数就是最上层的长度。
在这里插入图片描述

接下来考虑有增有减的情况,可以看到每次消去递增的最底层不会让情况变坏,而且可能会让情况变好。如果分组不在x列停止的话,x列会始终高于y列。最终最少组人数就会变为最上层的长度。应该尽可能降低x列及左边高度使得x列和y列持平,这样最上层的长度就变长了,结果更优(这里最上层长度有点类似于一个最差估计)。
在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;
map<int, int, less<int>> cnt;
map<int, int>::iterator it, jt, kt;
int n, t, ans = INT_MAX;
int main(int argc, char const *argv[]) {
  cin >> n;
  for (int i = 0; i < n; i++) {
    cin >> t;
    cnt[t]++;
  }
  for (; !cnt.empty();) {
    it = jt = kt = cnt.begin();
    int h = it->second, l = 1, ph = h;

    for (jt++;
         jt != cnt.end() && it->first + 1 == jt->first && ph <= jt->second;
         it++, jt++) {
      ph = jt->second;
      jt->second -= h;
      if (jt->second == 0) kt = jt;
      l++;
    }
    // it->second = 0;
    ans = min(ans, l);
    cnt.erase(cnt.begin(), ++kt);
  }
  cout<<ans;
  return 0;
}

解法二 二分查找

证明

二分查找要好理解点。优先增加符条件的最少人数组。
为了实现这个目标,需要维护一个递增的gro,并保持siz的组内单调性(gro数组值相等的区间,对应区间的siz要递减)。
gro[i]记录的是第i组需要选手的实力。
siz[i]记录的是第i组当前的人数。

用下面用数学归纳法证明。
一开始1个数字,肯定满足条件
下面证明k到k+1,组内单调性性质不变。
为了方便理解,这里就直接举例子了。
假设gro [ . . . x ] [...x] [...x],那么当前读入的选手实力有三种情况(实力事先已经升序排列)

  • 实力为 x − 1 x-1 x1

    • gro [ . . . y , x . . . x ] 且 y = x − 1 [...y,x...x] 且y=x-1 [...y,x...x]y=x1

      会将y加1变为x,由于后面的x都是由原来的y变来的,所以仍然能保持组内单调(单调的数列加上同一个数仍然单调)。

    • gro [ . . . y , x . . . x ] 且 y < x − 1 [...y,x...x] 且y<x-1 [...y,x...x]y<x1

      gro长度会加1,添加一个x,siz长度加1,添加1,组内单调性质不变。

  • 实力为 x x x

    gro变为 [ . . . x + 1 ] [...x+1] [...x+1],x+1组只有一个,显然符合。

  • 实力大于 x x x

    gro变为 [ . . . x , z ] [...x,z] [...x,z],新增的z组也只有一个,显然符合。

代码

#include <bits/stdc++.h>
using namespace std;
const int NN = (int)1e5 + 5;
int siz[NN], gro[NN], input[NN], n, top;
int main(int argc, char const *argv[]) {
  cin >> n;
  for (int i = 0; i < n; i++) {
    cin >> input[i];
  }
  sort(input, input + n);
  for (int i = 0; i < n; i++) {
    int v = input[i];
    // 优先加入后面
    int *pos = upper_bound(gro, gro + top, v);
    int ii = pos - gro - 1;
    if (ii >= 0 && gro[ii] == v) {
      gro[ii]++;
      siz[ii]++;
    } else {
      // 增加组
      gro[top] = v + 1;
      siz[top] = 1;
      top++;
    }
  }
  int ans = INT_MAX;
  for (int i = 0; i < top; i++) ans = min(ans, siz[i]);
  cout << ans;
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值