题解: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
cpp 复制代码
/*
思路一:
我们肯定要让分出的组数尽可能少
那么,我们考虑对于一个数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 

我们如果当一段被选择后就删除这些数所对应的*,那么如果这个数出现了不止一次,在它上面的*会由于重力作用落下来(???),从而支持我们继续操作
我们发现,最优策略不是看连续段越长越好,而是这样的(不同字母代表不同分组):
      C
    B 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;
}
```|
相关推荐
恣艺4 分钟前
LeetCode 124:二叉树中的最大路径和
算法·leetcode·职场和发展
猫头虎9 分钟前
如何在 macOS 上使用 dnsmasq 搭建本地 DNS 缓存/转发
开发语言·macos·缓存·golang·beautifulsoup·beego·go1.19
我命由我1234516 分钟前
Windows 操作系统 - Windows 设置始终使用 Windows 照片查看器打开图片
运维·windows·经验分享·笔记·学习·操作系统·运维开发
1白天的黑夜117 分钟前
前缀和-1314.矩阵区域和-力扣(LeetCode)
c++·leetcode·前缀和
weisian15120 分钟前
力扣经典算法篇-42-矩阵置零(辅助数组标记法,使用两个标记变量)
算法·leetcode·矩阵
恣艺33 分钟前
LeetCode 123:买卖股票的最佳时机 III
算法·leetcode·职场和发展
geoyster1 小时前
20250802-102508010-CP
算法
jdlxx_dongfangxing1 小时前
2023 年 NOI 最后一题题解
c++·noi
Q741_1471 小时前
优选算法 力扣 202.快乐数 快慢双指针 解决带环问题 C++解题思路 每日一题
c++·算法·leetcode·快慢双指针·环形问题
DONG9131 小时前
Python 中的可迭代、迭代器与生成器——从协议到实现再到最佳实践
开发语言·汇编·数据结构·python·算法·青少年编程·排序算法