LG P1582 倒水

題目描述

一天,CC買了N個容量可以認爲是無限大的瓶子,開始時每個瓶子裏有1升水。接着~~CC發現瓶子實在太多了,於是他決定保留不超過K個瓶子。每次他選擇兩個當前含水量相同的瓶子,把一個瓶子的水全部倒進另一個裏,然後把空瓶丟棄。(不能丟棄有水的瓶子)

顯然在某些情況下CC無法達到目標,比如N=3,K=1。此時CC會重新買一些新的瓶子(新瓶子容量無限,開始時有1升水),以到達目標。

現在CC想知道,最少需要買多少新瓶子才能達到目標呢?

輸入輸出格式

輸入格式:

一行兩個正整數, N,K( 1N2×109,K1000 )。

輸出格式:

一個非負整數,表示最少需要買多少新瓶子。

輸入輸出樣例

輸入樣例#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*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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章