P1582 倒水

题目描述

一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水。接着~~CC发现瓶子实在太多了,于是他决定保留不超过K个瓶子。每次他选择两个当前含水量相同的瓶子,把一个瓶子的水全部倒进另一个里,然后把空瓶丢弃。(不能丢弃有水的瓶子)

显然在某些情况下CC无法达到目标,比如N=3,K=1。此时CC会重新买一些新的瓶子(新瓶子容量无限,开始时有1升水),以到达目标。

现在CC想知道,最少需要买多少新瓶子才能达到目标呢?

输入输出格式

输入格式:

 

一行两个正整数, N,K( 1≤N≤2×109,K≤10001\le N\le 2\times 10^9,K\le 10001≤N≤2×109,K≤1000 )。

 

输出格式:

 

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

 

输入输出样例

输入样例#1: 复制

3 1

输出样例#1: 复制

1

输入样例#2: 复制

13 2

输出样例#2: 复制

3

输入样例#3: 复制

1000000 5

输出样例#3: 复制

15808
//转载一个大佬的解释....
/*
依题意可得,每2^x个瓶子可以合成一个瓶子。

以样例13 5来说,

13=8+4+1.

也就是说13个瓶子可以合并成3个瓶子,但此时不满足“小于k个”条件,所以需要购买空瓶子。

买1个,14=8+4+2,没有什么卵用。

买2个,15=8+4+2+1,好像更糟。

买3个,16=16,搞定。

根据上述过程可以得出初步思路:算出n可以分成几个2^x相加,也就是可以合成几个瓶子。如果结果>k那么买一个空瓶重复上述过程。

但是这里需要一个小技巧,如果你分解数的时候暴力枚举,时间肯定爆炸。

由于是2^x,所以我们很容易地想到2进制。所有2的倍数的二进制都是100000……(好多好多的0)

观察样例13的二进制: 1101.相当于二进制1000+100+1即十进制8+4+1.

得出结论,要统计有多少个因子(好像不叫因子,反正就那意思),只需要数数当前瓶子数的2进制下有多少个1即可。

那么我们需要一位位比较。如果把整个数转成二进制时间不说了。

如何快速的获得此数二进制数下的某一位呢?

我们只需要构造一个数,这个的二进制数是0000000000000000000100000000(1<<N)

然后再把当前数与该数按位与,就可以得出当前数二进制下某一位。(啥?你不知道啥叫按位与?)

交上去发现悲伤的超时了一个点。

所以我们需要优化。其实每一次加1的目的就是为了让数中的0(这里我觉得应该是1,理由是让最后1的个数控制在k个范围内)变少。就是需要进位。然后贴一下另一个大佬的代码(精简版)

#include <cstdio>
int n, k, ans;
int main() {
    scanf("%d%d", &n, &k);
    while(__builtin_popcount(n) > k) ans += n & -n, n += n & -n;
    printf("%d", ans);
}
__builtin_popcount(n):这个是用来求n这个数的二进制数中有多少个1.
ans+=n&-n:是加上最低位的1的数  能够计算出最少需要的1,比如13----01101&10011 = 1  最低位的1是1
(随便举个:01010&10110 = 10  最低位的1表示的数就是2)   
n+=n&-n:就是消除n中的1.
//每天进步一点点,加油!!!
*/
#include<cstdio>
using namespace std;
int main()
{
    long long n,k;
    scanf("%lld%lld",&n,&k);
    int count1=64;
    int num=0;
    while(1){
        count1=0;
        for(int i=0;i<64;i++){
            if((n&((long long)1<<i))>0){
                count1++;
            }
        }
        if(count1<=k){
            printf("%d\n",num);
            break;
        }
        else {
            num+=n&-n;
            n+=n&-n;
        }
    }
    return 0;
}

ps:各位大佬(萌新)看的时候如果觉得有问题,可以评论一番,我会及时改正,毕竟本人也是渣渣一枚(^_^)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值