题目描述
一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水。接着~~CC发现瓶子实在太多了,于是他决定保留不超过K个瓶子。每次他选择两个当前含水量相同的瓶子,把一个瓶子的水全部倒进另一个里,然后把空瓶丢弃。(不能丢弃有水的瓶子)
显然在某些情况下CC无法达到目标,比如N=3,K=1。此时CC会重新买一些新的瓶子(新瓶子容量无限,开始时有1升水),以到达目标。
现在CC想知道,最少需要买多少新瓶子才能达到目标呢?
输入输出格式
输入格式:一行两个正整数, N,K( $1\le N\le 2\times 10^9,K\le 1000$ )。
输出格式:一个非负整数,表示最少需要买多少新瓶子。
输入输出样例
第一眼真的看不出这是个数学题
感觉像是个搜索题。。。毕竟有个题叫倒水问题(郑公子本色出演bfs例题)。。。
昨天奥(yun)赛(dong)日(hui)(233)晚自习的时候看了十分钟没想出来,放学了。。。
今天下了早读去吃饭,想了一路,付钱的时候买鸡肉卷打了两块突然想到了怎么做- -
思路:
瓶子里的水增加只可能×2,也就是瓶子里的水量只有可能为2^m(m∈N)
注意到数据范围,n<=2*109,差不多也就是n<=231(用计算器算的。。。)
然后我们可以打一个表,是2^m对应的值,这个没什么好说的
然后我们可以贪心(但是我并不能证出这个贪心的正确性,但它确实A掉了这个题)
早读睡觉的时候突然想到二进制,然后想到了怎么证明这个贪心,这也引出了我的第二种这个题的思路(两种思路的代码我都会给出):
1.二进制思路
我们可以等效的认为是有n的水量,有k个瓶子,每个瓶子装的水量只可能为2^m
那么我们将n转换为一个二进制数,也就是说,一个瓶子可以消除掉一位上的1
我们只需看k是否大于等于n的二进制表示的1的数量
如果是,那么k个瓶子可以装完,输出0
否则,输出距离剩下的数的最近的一个2^m的值与剩下的的差值,即为仍需购买的瓶子的数量
2.递归实现的一种思路(当然也可以用两层for循环)
算出最后剩下的第i个瓶子盛水量(要给后几个瓶子每个瓶子至少空出1的位置,但是这步现在一想应该是多此一举了,而且我试了一下确实是无所谓的一步,所以我给出两种代码)
找到小于等于剩余水量的2^m的值,然后减去,搜下一层
最后一瓶如果没有恰好匹配的2m,说明这几瓶不能装完,那就用找到剩余的对应的k值,输出2(m+1)-搜到最后一瓶时剩的水量,就是要再买多少瓶水
如果有,那就不需要再买了,输出0就行了
代码1
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define For(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
const unsigned long long cf[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824,2147483648,4294967296};
long long n;
int wws,k,s;
bool num[32];
int main()
{
scanf("%lld %d",&n,&k);
while(n)//进制转换
{
num[++wws]=n%2;//倒着存它的二进制表示
if(num[wws])
++s; //它的二进制表示里1的个数
n/=2;
}
if(s<=k) //如果可以直接盛完
{
printf("0");
return 0;
}
for(;k;--wws) //如果不能,先消去盛了的那些“1”
{
if(num[wws])
--k;
num[wws]=0;
}
long long int temp=1;
For(i,1,wws) //转回十进制
{
if(num[i])
n+=temp;
temp<<=1; //这里temp恰好可以符合“距离剩下的数的最近的一个2^m的值”
}
printf("%lld",temp-n); //输出这个差值
return 0;
}
代码2
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define For(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
const unsigned long long cf[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824,2147483648,4294967296};
long long int n,ans,ta[1001];
short int k;
void dfs(int dpt)
{
for(int i=31;i>=0;--i)
if(cf[i]<=n)
{
if(dpt==k)
{
if(cf[i]==n)
printf("0");
else
printf("%lld",cf[i+1]-n);
return;
}
n-=cf[i];
if(!n)
{
printf("0");
return;
}
dfs(dpt+1);
break;
}
}
int main()
{
scanf("%lld %d",&n,&k);
dfs(1);
return 0;
}