[AHOI2018初中组]分组 题解

题目链接: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;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值