问题描述
有一个可重集合,一开始只有一个元素 0。
你可以进行若干轮操作,每轮你需要对集合中每个元素 x 执行以下三种操作之一:
- 将 x 变为 x+1;
- 选择两个非负整数 y,z 满足 y+z=x ,将 x 变为这两个元素;
- 什么都不做。
给出一个最终的集合,求至少经过几轮才能得到这个集合。
输入格式
第一行一个正整数 n,表示最终集合的大小。
第二行 n 个非负整数 ai,表示最终集合中的元素。
输出格式
输出一个整数,表示答案。
样例输入
5
0 0 0 3 3
样例输出
5
解析
不妨倒过来思考,考虑如何通过将一个数减一和合并两个数使目标集合变成只有0的集合。对于任意两个数(a)和(b),如果将两个数分开减为0再合并,那么操作次数为(max(a,b)+1),而先合并再减小为0的操作次数为(a+b+1)。显然第一种更优。推广开来,我们的最优策略即为每轮先和并所有的0,再把不是0的减1,直到只剩一个0为止。
具体实现也很简单。每次把0的数量减半,同时减小长度。预处理每个数在哪一轮会减为0,并在那一轮最后加上新的0的个数。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 1000002
using namespace std;
int n,i,a[N],cnt[N],num,ans;
int main()
{
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
cin>>n;
for(i=1;i<=n;i++) cin>>a[i];
for(i=1;i<=n;i++) cnt[a[i]]++;
num=cnt[0];
while(n>1){
ans++;
n-=num/2;
num=(num+1)/2;
num+=cnt[ans];
}
cout<<ans<<endl;
fclose(stdin);
fclose(stdout);
return 0;
}