您的位置:首页 > 其它

2017广东工业大学程序设计竞赛决赛E题 倒水(water)

2017-03-29 19:41 477 查看
题目地址:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=4


Problem E: 倒水(Water)


Description

一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水。接着~~CC发现瓶子实在太多了,于是他决定保留不超过K个瓶子。每次他选择两个当前含水量相同的瓶子,把一个瓶子的水全部倒进另一个里,然后把空瓶丢弃。(不能丢弃有水的瓶子)

显然在某些情况下CC无法达到目标,比如N=3,K=1。此时CC会重新买一些新的瓶子(新瓶子容量无限,开始时有1升水),以到达目标。

现在CC想知道,最少需要买多少新瓶子才能达到目标呢?


Input

第一行一个整数T,表示有T组数据。

接着T行,每行两个正整数, N,K(1<=N<=10^9,K<=1000)。


Output

一个非负整数,表示最少需要买多少新瓶子。


Sample Input

3

3 1

13 2

1000000 5


Sample Output

1

3

15808

1、首先这么想,一开始,如果我们有偶数个瓶子,那我们就可以直接两两倒在一起,瓶子数减少一半,并且,瓶子里的水量乘2,如果此时瓶子数依然是偶数,重复前面的操作。

2、然而事实不是那么顺利,假如现在我们有奇数个一升的瓶子,我们可以先单独拿出那一瓶,暂时成为光棍瓶,暂时保留不做处理,然后把剩下的偶数瓶按第一段的说法做。

3、也就是这样,如果当前有偶数个瓶子,我们直接两两倒在一起,瓶子数减半。如果当前有奇数个瓶子,我们先拿出来一个,暂时保留,再把剩下的偶数个两两倒在一起。

4、好了,如果我们一直重复上面的操作,最后一定会剩下一瓶,没办法再倒了,加上操作过程中拿出来的那些瓶子。现在的任务就是把这些瓶子的数量在想办法减少,借助新的一升的瓶子,问需要多少个新的一升的瓶子。

5、为了方便,我用了优先队列。在第4段的描述中,我们产生了许多个光棍瓶,如果光棍瓶的数量小于等于题目中的k,我们直接输出0就可以了。否则,我们就得想办法减少瓶子的数量。

6、假设,假设我们剩下了4个光棍瓶,分别有2、4、8、32升,(第一段可知,所有的光棍瓶水量必定是2的倍数,注意,1看做 2的0次方),这4个光棍瓶,我们先把他变成3瓶,怎么变呢,把最小的两瓶倒在一起,不是直接倒,是需要新的一升的瓶子创造一个4升的瓶子,和原有的4升倒在一起,达到目的。想得到一个4升,那就需要两个2升,想得到两个2升,就需要4个1升,以此类推,我们需要四个新的一升的瓶子。在类推过程中,我们发现,我们本来有一个2升的瓶子,所以我们只需要2个新瓶子,即4-2=2,我更愿意理解为2^2-2^1=2,(2的平方-2的1次方)。看图。有点像二叉树,本来4应该由四个1组成,而我们已经有了一个2升,就可以少创造一个2升的瓶子了。



代码如下:

#include<stdio.h>
#include<queue>
using namespace std;
priority_queue<int, vector<int>, greater<int> > q; //优先队列,先出小的元素
int pow2[33];//用来记录2的i次方是几,比如pow2[i]表示2的i次方
int sum;//需要买的瓶子数
int t,n,k;
void calcu()//计算瓶子数,每执行一次就把最小的两瓶倒在了一起
{
if(q.size()==k)//当队列里的瓶子数量等于k,达到题意
{
return;
}
//下面取出两个最小的瓶子
int u=q.top();
q.pop();
int v=q.top();
q.pop();
//把取出的两瓶,想办法倒在一起;

sum+=pow2[v]-pow2[u];//关键的计算

//这样就把2^u 的瓶子变成了2^v 那么大,然后倒在一起,放回队列里
q.push(v+1);
calcu();//继续计算
}

int main()
{
scanf("%d",&t);

pow2[0]=1; //2的0次方等于1;
for(int i=1;i<=30;i++)
pow2[i]=2*pow2[i-1];

while(t--)
{
while(!q.empty())
{
q.pop();
}
scanf("%d%d",&n,&k);
for(int i=0; n; i++)//当n等于0时,说明已经没有可以再倒在一起的了
{
if(n%2==1)//有奇数个相等水量的瓶子,余一个不能倒的,暂时放在队列里
{
q.push(i);//剩余一个2的i次方的水瓶
}
n/=2; //将偶数个瓶子两两倒在一起,瓶子减少一半
}

sum=0;
calcu();

printf("%d\n",sum);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐