集合
【问题描述】
给定一个可重集合,一开始只有一个元素0。然后你可以操作若干轮,每一轮,你需要对于集合中的每个元素𝑥进行如下三种操作之一:
1、将𝑥变为𝑥 + 1。
2 、将𝑥分裂为两个非负整数𝑦, 𝑧,且满足𝑥 = 𝑦 + 𝑧。
3 、什么都不做。
每一轮,集合中的每个元素都必须进行上面三个操作之一。对于一个最终的集合,你的任务是判断至少进行了多少轮。
【输入文件】
第一行为一个正整数𝑛,表示集合的最终大小。第二行为𝑛个非负整数,描述集合中的元素。
【输出文件】
输出一个非负整数,为最少的轮数。
【输入输出样例】
multiset.in
multiset.out
5
0 0 0 3 3
5
【数据规模和约定】
设集合中最大的元素为𝑚。
对于10%的数据,满足𝑛, 𝑚 ≤ 10。对于30%的数据,满足𝑛, 𝑚 ≤ 100。
对于50%的数据,满足𝑛 ≤ 1000,𝑚 ≤ 10000。对于100%的数据,满足1 ≤ 𝑛, 𝑚 ≤ 1000000。
采用贪心的思想,从最终的数列倒着往0推,为了更快地得到0,我们每次需要将非0的数进行-1操作,对于已经为0的两个数进行合并(与题目中的操作桥好像反),如果0的个数为奇数个,那么最后一个0不进行操作。
具体代码实现,我们如果将这些数每次都扫一遍,肯定会炸,因为很大一部分数是进行-1操作,所以我们与其将他们-1,不如将标准+1,也就是每次将一个递加的i看做0,那么只需要考虑0的个数就好了,并且将i的个数加到0的个数中去,将上一次0的个数除以2取上整(今天学到一种取上整的小技巧,(i+1)/2)。
所以我们用一个c【i】存i的个数,顺便记录最大的i,然后先用一个循环进行max(i)次操作,再用一个循环将未合并的0合并。两个循环都用ans来记录进行的次数,最后ans即为答案。
附上代码
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 int n,maxx,a[1000010],c[5000010],ans; 5 int main() 6 { 7 scanf("%d",&n); 8 for(int i=1;i<=n;++i) 9 { 10 scanf("%d",&a[i]); 11 maxx=max(maxx,a[i]); 12 c[a[i]]++; 13 } 14 for(int i=1;i<=maxx;++i) 15 { 16 ans++; 17 c[0]=c[i]+(c[0]+1)/2; 18 } 19 for(;c[0]>1;c[0]=(c[0]+1)/2) 20 ans++; 21 printf("%d\n",ans); 22 return 0; 23 }