LG P1582 倒水

题目描述

一天,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$ )。

输出格式:

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

输入输出样例

输入样例#1: 复制
3 1
输出样例#1: 复制
1
输入样例#2: 复制
13 2
输出样例#2: 复制
3
输入样例#3: 复制
1000000 5
输出样例#3: 复制
15808

第一眼真的看不出这是个数学题
感觉像是个搜索题。。。毕竟有个题叫倒水问题(郑公子本色出演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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值