题解:P4447 [AHOI2018初中组] 分组
P4447 [AHOI2018初中组] 分组
题目描述
小可可的学校信息组总共有 nnn 个队员,每个人都有一个实力值 aia_iai。现在,一年一度的编程大赛就要到了,小可可的学校获得了若干个参赛名额,教练决定把学校信息组的 nnn 个队员分成若干个小组去参加这场比赛。
但是每个队员都不会愿意与实力跟自己过于悬殊的队员组队,于是要求分成的每个小组的队员实力值连续,同时,一个队不需要两个实力相同的选手。举个例子:[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] 不是合法的分组方案,因为实力值不连续;[0,1,1,2][0, 1, 1, 2][0,1,1,2] 同样不是合法的分组方案,因为出现了两个实力值为 111 的选手。
如果有小组内人数太少,就会因为时间不够而无法获得高分,于是小可可想让你给出一个合法的分组方案,满足所有人都恰好分到一个小组,使得人数最少的组人数最多,输出人数最少的组人数的最大值。
注意:实力值可能是负数,分组的数量没有限制。
输入格式
输入有两行:
第一行一个正整数 nnn,表示队员数量。
第二行有 nnn 个整数,第 iii 个整数 aia_iai 表示第 iii 个队员的实力。
输出格式
输出一行,包括一个正整数,表示人数最少的组的人数最大值。
输入输出样例 #1
输入 #1
7
4 5 2 3 -4 -3 -5
输出 #1
3
说明/提示
样例解释
分为 222 组,一组的队员实力值是 [4,5,2,3][4, 5, 2, 3][4,5,2,3],一组是 [−4,−3,−5][-4, -3, -5][−4,−3,−5],其中最小的组人数为 333,可以发现没有比 333 更优的分法了。
数据范围
对于 100%100\%100% 的数据满足:1≤n≤1051\leq n\leq 10^51≤n≤105,∣ai∣≤109|a_i|\leq10^9∣ai∣≤109。
本题共 101010 个测试点,编号为 1∼101\sim101∼10,每个测试点额外保证如下:
测试点编号 | 数据限制 |
---|---|
1∼21\sim21∼2 | n≤6,1≤ai≤100n\leq 6, 1\leq a_i \leq 100n≤6,1≤ai≤100 |
3∼43\sim43∼4 | n≤1000,1≤ai≤105n\leq 1000, 1\leq a_i\leq 10^5n≤1000,1≤ai≤105 且 aia_iai 互不相同 |
5∼65\sim65∼6 | n≤105n\leq 10^5n≤105,aia_iai 互不相同 |
7∼87\sim87∼8 | n≤105,1≤ai≤105n\leq 10^5, 1\leq a_i \leq10^5n≤105,1≤ai≤105 |
9∼109\sim 109∼10 | n≤105,−109≤ai≤109n\leq 10^5, -10^9 \leq a_i \leq 10^9n≤105,−109≤ai≤109 |
/*
思路一:
我们肯定要让分出的组数尽可能少
那么,我们考虑对于一个数x,它最理想的情况一定是放到一个结尾为x-1的组中;如果没有就只能新开一组
因为有大小顺序的限制,那么考虑首先升序排序,再用set存储当前每个组的结尾元素,每遇到一个数就在set中查找/更新即可
set要存两个信息:组的结尾元素和长度,排序规则按照先结尾元素升序,后长度升序(长度升序可以使最小长度更大)
答案就是set中长度最小的组
注意到组可以重复,所以应该用multiset
本质上是一个贪心+模拟的做法-------------------------------------------------------------------------------------------------------------------------思路二:究极数形结合
我们如果画一条数轴,那么题目说的分组,就相当于在数轴上选一个连续段:****** ********
---------------------------------->-5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 如果数字条件变复杂(同一个值的数字会有多个),我们考虑用类似条形统计图的方式来刻画:
* *
* * * *
* * * * * *
------------------------------->
-5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 我们如果当一段被选择后就删除这些数所对应的*,那么如果这个数出现了不止一次,在它上面的*会由于重力作用落下来(???),从而支持我们继续操作
我们发现,最优策略不是看连续段越长越好,而是这样的(不同字母代表不同分组):CB B
A A A A C
这种方法不会导致最上面的C被孤立,而会和下面两个B平均起来,最终分组大小都是2可以总结出分组选择的方式:
如果右边一列的高度不低于当前列,则继续加入右边一列最下方的数。反之,停止连续选择。
这样,最靠左的一个“峰”相较其右边一列的高度差就不断减小,直到相同。如此反复。记录所画所有线的最短长度,即为答案。
可以用 std::map 关键字自动排序的特性来记录图形。正确性比较显然 (吗?)参考 https://www.luogu.com.cn/article/6bk13dsc
*/#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;int n, a[maxn];void solve()
{cin >> n;for(int i = 1; i <= n; i ++){cin >> a[i];}sort(a + 1, a + n + 1); // 注意排序multiset<pair<int, int>> s; // first:结尾元素 second:当前长度for(int i = 1; i <= n; i ++){if(s.empty()){ // 注意判空s.insert({a[i], 1}); continue;}auto it = s.lower_bound({a[i] - 1, 0});if(it == s.end() || it->first != a[i] - 1){ // 查找失败s.insert({a[i], 1});}else{s.insert({a[i], it->second + 1}); // 查找成功s.erase(it);}}int minn = 1e9;for(auto it : s){minn = min(minn, it.second);}cout << minn << '\n';
}map<int, int> vis;void solve2()
{cin >> n;for(int i = 1; i <= n; i ++){int x; cin >> x; vis[x] ++; // 初始化}int ans = 1e9;while(!vis.empty()){auto it = vis.begin(); // 取出当前最小的元素if(it->second == 0){vis.erase(it); continue; // 如果已经没了,就删掉它,并继续}int cnt = 1;while(next(it) != vis.end() && next(it)->first == it->first + 1 && next(it)->second >= it->second){ // 要满足:存在next(it)、大小是连续的、下一个的高度不低于当前列(说过的可以继续延伸的条件)cnt ++; it = next(it);}ans = min(ans, cnt);it = vis.begin();for(int i = 1; i <= cnt && it != vis.end(); i ++, it = next(it)){ // 把成功延伸的部分个数减一it->second --;}}cout << ans << '\n';
}signed main()
{ios :: sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);solve();
// solve2();return 0;
}
```|