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

本篇介绍了一个ACM竞赛的经典问题——倒水问题。该问题要求通过合并水量相同的瓶子来达到指定的瓶子数量。文章详细解析了贪心算法的思路与实现过程,并给出了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

倒水(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 0x7fffffff
using 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;
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值