倒水(rms2017模拟2-1)* 【推理】

来源:互联网 发布:编程cim是做什么的 编辑:程序博客网 时间:2024/05/17 05:50

倒水(water.cpp)
试题描述: 某一天,在 NH 学习 CS 的 ZR 习得新的 ACM 秘籍,惊奇的发现,每使用一次会额外
得到一个容量无限大的瓶子,并且初始时每个瓶子里有 1 升水。当他一口气使用了 N 次秘 籍后发现瓶子实在太多了,于是他决定保留不超过 K 个瓶子。规则是每次选择两个当前含 水量相同的瓶子进行合并,把一个瓶子的水全部倒进另一个瓶子,然后把空瓶砸碎。 显然在某些情况下 ZR 无法达到目标,比如 N=3,K=1。此时 ZR 会重新使用秘籍获得
一些新的瓶子(新瓶子容量无限,开始时有 1 升水),以达到目标。 现在 ZR想知道,除去一开始使用了N次秘籍,最少还需要使用多少次秘籍才能达到目
标?
输入格式
一行两个正整数N,K(1<=N<=109,K<=1000)。
输出格式
一个非负整数,表示最少需要买多少新瓶子。
输入样例
3 1
输出样例
1
数据规模
对于 30%的数据,N<=3*105;
对于 100%的数据如题目。

题解
题目要求只能把水量相同的合并,于是可以贪心地把能合并的都合并,可以发现最终的水量是一些2的幂次,而这些数值的和也就是瓶子的数量N。那么最后剩下的瓶子个数就是N二进制中“1”的个数。如果“1”的个数小于等于K,那就满足要求了。如果大于K,假设N的二进制中最后一个“1”位置右数第K个,那么就添加2K-1个新瓶,直到使得N的二进制中“1”的个数减少到K个或以下。求某个十进制下的数字在二进制下有多少个“1”,可以不断减LowBit的方法来求。
假设x的二进制中最后一个“1”位置右数第y个LowBit(x) = 2y-1.

时间复杂度:O(K)
空间复杂度:O(1)

代码

#include<bits/stdc++.h>#define F(i,a,b) for( int i=(a);i<=(b);i++ )#define N 1001#define M 10001#define LL long long#define oo 0x7fffffffusing namespace std;LL read(){    LL f=1,s=0;    char ch=getchar();    while( ch>'9' || ch<'0' ) { if( ch=='-' ) f=-1; ch=getchar(); }    while( ch<='9' && ch>='0' ) { s=( s<<1 )+( s<<3 )+ch-'0'; ch=getchar(); }    return f*s;}LL m,n,k,t;LL tot,ans;LL a[N],b[N];int fa,cnt;LL init(){    b[0]=1;    F( i,1,oo )    {        b[i]=b[i-1]*2;        if( b[i]>=n )            break;    }    while( n>1 )    {        a[cnt]=n%2;        n/=2;        cnt++;     }     a[cnt]=1;}LL cul( int x ){    a[x]+=1;    while( a[x]==2 )    {        a[x]=0;        x++;        a[x]++;    }    int mmax=max( x,cnt );    for( int i=mmax;i>=fa;i-- )    {        if( a[i] ) t+=b[i];    }    return t-m;} int main(){    freopen( "water.in","r",stdin );    freopen( "water.out","w",stdout );    n=read();    k=read();    m=n;    init();    for( int i=cnt;i>=0;i-- )    {        if( a[i] )         {            tot++;            if( tot==k+1 )             {                ans=cul( fa );                break;             }             fa=i;        }    }    cout<<ans<<endl;     return 0;}