【洛谷1582】倒水
原题:
首先注意看清题,题目并不是要求拼出n体积的水,而是现在有n个1L水,要求将瓶压缩至不超过k个
首先可以考虑一下如果不引入新瓶,原来的瓶子最少能压缩到多少个
先尝试两种策略,一种是尽量找大的合并,如1 1 1 1 1 1 1-> 2 2 2 1 -> 4 2 1
另一种是尽量找小的合并,如1 1 1 1 1 1 1 -> 2 2 2 1 -> 4 2 1
其实是没得区别的233
最后的结果都是把原来的瓶子数n的每一个二进制位拆出来
由此可以联想到瓶子内的水总是2的幂次,合并就相当于把两个同位的1合并成一个高位的1
那么n个瓶子最少能压缩到多少个,本质上是问十进制数n最少能用多少个二进制位的1表示
显然答案就是n的二进制位个数
形式化的证明似乎也不难,不过这种结论比较显然,大力猜想就行了
n的二进制位个数可能仍然大于k,此时需要不同位的两个1压缩
只能购买新瓶把低位合并到和高位的1同位再合并,才能让瓶数-1
新买的瓶子首先要能拼成和低位的1同位的1
可以用lowbit快速取到最低位的1,让其提高一位的代价就是lowbit(n)
买lowbit(n)个瓶子拼成一个容量为lowbit(n)的瓶子,即可跟lowbit(n)的瓶子合并
如此循环,直到低位的1被提高到和更高位的1同位时,总瓶数就-1
不必特意判断增加lowbit(n)后瓶数是否-1,只需让n+=lowbit(n),然后重新求n的二进制位1的个数
直到个数<=k为止
注意一个坑,n<=2e9可以int装,但是假设k=1,那么最后拼出的瓶子体积大概达到4e9,需要longlong或者uint
代码:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 long long n,m,o; 5 int gto(long long x){ 6 int z=0; 7 for(;x;x>>=1) z+=(x&1); 8 return z; 9 } 10 long long lbt(long long x){ return x&-x;} 11 int main(){ 12 cin>>n>>m; 13 o=gto(n); 14 long long ans=0; 15 while(o>m){ 16 ans+=lbt(n); 17 n+=lbt(n); 18 o=gto(n); 19 } 20 cout<<ans<<endl; 21 return 0; 22 }