題目描述
一天,CC買了N個容量可以認爲是無限大的瓶子,開始時每個瓶子裏有1升水。接着~~CC發現瓶子實在太多了,於是他決定保留不超過K個瓶子。每次他選擇兩個當前含水量相同的瓶子,把一個瓶子的水全部倒進另一個裏,然後把空瓶丟棄。(不能丟棄有水的瓶子)
顯然在某些情況下CC無法達到目標,比如N=3,K=1。此時CC會重新買一些新的瓶子(新瓶子容量無限,開始時有1升水),以到達目標。
現在CC想知道,最少需要買多少新瓶子才能達到目標呢?
輸入輸出格式
輸入格式:一行兩個正整數, N,K( )。
輸出格式:一個非負整數,表示最少需要買多少新瓶子。
輸入輸出樣例
第一眼真的看不出這是個數學題
感覺像是個搜索題。。。畢竟有個題叫倒水問題(鄭公子本色出演bfs例題)。。。
昨天奧(yun)賽(dong)日(hui)(233)晚自習的時候看了十分鐘沒想出來,放學了。。。
今天下了早讀去吃飯,想了一路,付錢的時候買雞肉卷打了兩塊突然想到了怎麼做- -
思路:
瓶子裏的水增加只可能×2,也就是瓶子裏的水量只有可能爲2^m(m∈N)
注意到數據範圍,n<=2*10^9,差不多也就是n<=2^31(用計算器算的。。。)
然後我們可以打一個表,是2^m對應的值,這個沒什麼好說的
然後我們可以貪心(但是我並不能證出這個貪心的正確性,但它確實A掉了這個題)
早讀睡覺的時候突然想到二進制,然後想到了怎麼證明這個貪心,這也引出了我的第二種這個題的思路(兩種思路的代碼我都會給出):
1.二進制思路
我們可以等效的認爲是有n的水量,有k個瓶子,每個瓶子裝的水量只可能爲2^m
那麼我們將n轉換爲一個二進制數,也就是說,一個瓶子可以消除掉一位上的1
我們只需看k是否大於等於n的二進制表示的1的數量
如果是,那麼k個瓶子可以裝完,輸出0
否則,輸出距離剩下的數的最近的一個2^m的值與剩下的的差值,即爲仍需購買的瓶子的數量
2.遞歸實現的一種思路(當然也可以用兩層for循環)
算出最後剩下的第i個瓶子盛水量(要給後幾個瓶子每個瓶子至少空出1的位置,但是這步現在一想應該是多此一舉了,而且我試了一下確實是無所謂的一步,所以我給出兩種代碼)
找到小於等於剩餘水量的2^m的值,然後減去,搜下一層
最後一瓶如果沒有恰好匹配的2^m,說明這幾瓶不能裝完,那就用找到剩餘的對應的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;
}