推了一个多小时的式子,ac后一看题解,7行代码搞定
emmmm我还是太菜了
蒟蒻解法:
不管怎么倒水,最终所有瓶子里面的水的数量一定可以用2k表示出来。
n最终可以合并成几个瓶子呢?
我们可以把n分解为多个2k相加的形式,例如:13=23+22+20,所以13最少合并到3个瓶子里面
求n最少能合并到几个瓶子里面,正是看n的二进制表示里面有多少个1
如果1的数量cnt小于等于k,则直接输出0。
否则:从第一个是1的位置+1开始,扫到cnt-k+1个1的位置,中间如果是0,ans就加上2是0的位置,最后ans+2第一个是1的位置
粗略的解释一下
假设某个数的二进制表示是1000101001000,k=2
加上23:1000101010000
加上24:1000101100000
加上25:1000110000000
加上27:1001000000000
对比刚开始的n:1000101001000
标0的是加完后最后一个1所在的位置。
我们发现在原始的n的第一个1开始,一直到cnt-k+1的1的位置,中间是0的地方都要在答案上加2k(当然第一个是1的地方除外)
maybe代码会好懂一些
#include<bits/stdc++.h> using namespace std; int n,a[35],b[35],cnt,k,now,ans;//a[i]记录n的二进制表示中,2^i所对应的位上是0还是1,b[i]记录第i个1的位置 long long mi[35];//mi[i]为2^i int read() { char ch=getchar(); int x=0; bool f=0; while(ch<'0'||ch>'9') { if(ch=='-') f=1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return f?-x:x; } void init() { mi[0]=1; for(int i=1;;i++) { mi[i]=mi[i-1]*2; if(mi[i]>n)break;//mi不开long long见祖宗 } } int main() { n=read();k=read(); init(); while(n) { a[now]=n&1;//now代表当前是2^now所代表的位置 if(n&1) { b[++cnt]=now; } now++; n>>=1; } if(cnt<=k)//特判 { printf("0");return 0; } int m=cnt-k+1; ans+=mi[b[1]]; for(int i=b[1]+1;i<=b[m];i++) { if(a[i]==0)ans+=mi[i]; } printf("%d ",ans); }
大佬做法:
因为所有的水都是由两份相同的水合并而成的,因此每瓶水的体积一定是2^i,(iin N)2i,(i∈N)升。
最后保留k个瓶子,那么最后总的升数的二进制表示中,1的个数一定<=k。
那么我们只要贪心地往n上加上lowbit(n)即可。
这个lowbit就是树状数组那个lowbit啦
简化代码的trick:
使用内置函数\_\_builtin\_popcount()
来计算一个数的二进制表示中1的数量。
这样下来,代码简化到仅剩7行。
惊不惊喜,意不意外?
------------------------摘自洛谷第一篇题解
#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); }