当前位置:C++技术网 > 资讯 > 不知道银行卡金额时怎样以最少的次数将银行卡所有钱转账到余额宝[续]

不知道银行卡金额时怎样以最少的次数将银行卡所有钱转账到余额宝[续]

更新时间:2015-11-17 02:38:04浏览次数:1+次

    购物的时候,经常能看到各种商品标价为12.99、3.09、4.00。不经意间留下一个思维定势:

钱要用浮点数表示。这是一个错觉。其实金额是一个定点数。只需要更换一下单位:12.99元其实是1299分

对于一个定点数,使用浮点数来表示是不科学的。


    简略的描述一下问题:

        银行卡中余额不明,找到一个合理的策略,在取钱次数尽可能少的情况下,把卡里面的钱取光。

        每次取钱的金额在区间[1, 5000000]。

        要求输出:每次取钱的金额和取出来的钱的总额。


    毫无疑问,卡里面钱多于5000000的时候,每次都取5000000就可以了。


    问题在于:

        当卡里面的钱少于5000000的时候,应该怎么取钱


    假设现在卡里面有4980.88元,也就是498088分。

    我们来看看这个数是由什么组成的:

        498088*1?

        249044*2?

        166029*3+1?

    不不不,这样太繁琐了。应该通过按权展开 

        4*100000+9*100000+8*1000+0*100+8*10+8*1

    那么每次取10000能取4次,会剩下9088;

    这时候,再次取100000就取不了了,卡里面的钱比100000少。那就取少点吧。

    不取100000,取10000。能取出来,而且能连续取9次。又出现不能取钱,老套路了。

    不能取10000,那就取1000。连续取8次。又出现不能取钱,套路+1。

    不能去1000,去个100总可以把,!!!!怎么回事还是取不出来。不要紧。

    再少点,每次取10,OK了。能连续操作8次。

    最后每次取1,连续操作8次。

    把卡里面钱全部取出来。

    我们来看看取了多少次钱。

        4+9+8+0+8+8=36

   这个方法能把卡里面的钱取光,而且我们意外的发现取钱的次数刚刚好是498088每一位的数字的和。这个发现很有意思,先留意一下。


    能把钱取光还不够,我们希望取钱的次数尽可能少。评估一下这个策略。评估这个策略比较有参考性的方法是计算出这个策略平均需要执行多少次取钱操作。

    这个策略是用于计算卡里面的钱少于5000000的时候的取钱操作。

        * 卡里面的钱有4999999种可能(可能是1分钱、可能是2分钱...可能是468147分钱...可能是4999999分钱);

        * 我们把每一种可能需要执行取钱操作的次数加起来(1分钱的时候执行1次取钱操作,55468分钱的时候执行5+5+4+6+8次取钱操作);

        * 再除以49999999就得到平均值。

    计算过程略(有兴趣的同学可以尝试用编程计算出这个总数),计算结果总共需要执行145000000次取钱操作,那么这种策略的平均取钱操作次数是:29。看起来不算很差。


    是不是还存在更好的策略呢?这个策略取钱次数是每一位上的数字之和。那么如果每一位上的数字比较小这和是不是会有所不同?怎么让每一位上面的数字变小?换进制!十进制每一位上最大的数字是9,换一个进制,丧心病狂一点换成二进制,每一位上最大的数字只是1。好像有点靠谱,马上验证一下。


    依旧假设卡里面有498088。(别高兴得太早,单位是分。死穷鬼!大雾)

    先转换为二进制:1111001100110101000

    按照前面的思路,取钱的次数将刚刚好是1111001100110101000每一位的数字的和。一共是10次。原来需要36次。哎哟这个叼。

    先别急着高兴,万一这个是特例呢?可能性虽小但不容忽视。再评估一下这个策略的平均值。

    计算过程略(还有没有同学感兴趣试试看编程计算出这次的总数是多少),计算结果总共需要执行54717312次取钱操作,平均值:10。OK!二进制果然是叼到飞起。


    总结如下:

        十进制的时候,

            最大值是4999999,

            初始取钱1000000,

            每当无法取出1000000就除以10,即取钱100000;

        二进制的时候,

            最大值是10011000100101100111111b,

            初始取钱10000000000000000000000b,(十进制为4194304)

            没当无法取出10000000000000000000000b就除以2,即取钱1000000000000000000000b

    废话如上,代码如下:



#include <stdio.h>
#define N 4194304
#define M 5000000

int MoveMeney(int nMeney)
{
	int count = 0;
	int sum = 0;
	//多于5000000的处理
	while (nMeney >= M)
	{
		nMeney -= M;
		sum += M;
		++count;
		puts("转款:50000.00");
	}
	//少于5000000的处理
	int sub = N;
	while (nMeney)
	{
		if (nMeney >= sub || sub == 1)
		{
			nMeney -= sub;
			++count;
			sum += sub;
			printf("转款:%d.%02d\n", sub / 100, sub % 100);
		}
		else
		{
			sub /= 2;
		}
	}
	printf("总额: %d.%02d\n", sum / 100, sum % 100);
	return count;
}

int main()
{
	double test = 254980.88;
	printf("转账次数%d\n", MoveMeney((int)(test * 100)));
	return 0;
}


最后,对于计算转账次数可以使用这样的函数:

int CalcMoveTime(int nMeney)
{
	int count = nMeney / M;
	nMeney %= M;
	while (nMeney)
	{
		++count;
		nMeney &= nMeney - 1;
	}

	return count;
}


不多做说明,作为课后作业让大家参考理解。