luogu P1582 倒水

O(logn)解法:

根据题目进行分析,不难发现2^n个瓶子可以合并到一个瓶子里(此n非彼n,不是题目里的n,而是一个任意的数),于是可以想到可以把n(题目所给的n)拆成多个2的次方相加的形式,即把n转换成二进制,然后统计有多少个1,1的个数就是不买新瓶子时所能达到的最优情况中瓶子的个数(即把瓶子合并成最少时瓶子的个数),用一个变量num来记录这个数。如果num<=k,那么就不用再买新瓶子,如果num>k,我们就需要通过买新瓶子来减少1的个数。拿第二个样例来分析,n=13,k=2,把n转化成二进制:1101,为了使买的瓶子最少,我们从最小位(最右边的位)往左扫,如果当前i位置对应的二进制值为1,那我们就把n加上2^(i-1)(为什么是i-1?因为二进制中从右往左数第i位对应的值就是2^(i-1)),这样二进制数就变成了1110,可以发现1的个数并没有减少,但是继续往左扫,当i=2时,第i位(以后第几位都是指从右往左数)的二进制值为1,那就给n加上2^(2-1),此时n变成了10000,1的个数就减少了。此时1的个数为1,小于k,于是答案为2^(1-1)+2^(2-1)=3。不难发现,当第i位为1时,左边与它相连的(不包括他本身)有多少个1,在加上2^(2-1)后就会减少多少个1,这就是解本题的原理。也许你会想,当n=13时,直接加上2^(3-1)不就满足条件了吗,为什么一定要从右往左扫?可以发现2^(3-1) > 2^(1-1)+2^(2-1),所以从右往左扫依然最优。并且这个性质具有通性:2^i > ∑ 2^j(0<j<i)。

#include <iostream>
#include <cstdio>
using namespace std;
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-')  f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+c-'0';
		c=getchar();
	}
    return f*x;
}
int n,k,ans,num,pre1,pre2;
int main()
{
	n=read();  k=read();
	for(int i=0;i<=31;i++)  if((n&(1<<i)))  num++;
	for(int i=0;i<=31;i++)
	  if((n&(1<<i)))
	  {
	  	if(num<=k)  break;
	  	ans+=(1<<i);
	  	pre1=i;  pre2=k;
		while((n&(1<<(i+1))))
		{
			i++;
			num--;
			if(num<=k)  break;
		}
		n+=(1<<pre1);
		if(num<pre2)  i--;
	  }
	cout<<ans;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值