您的位置:首页 > 编程语言 > Go语言

英雄会(csdn pongo)题解之罐子和硬币

2014-02-12 22:21 337 查看
这道题是春节前在家做的,当时题解写了一半,今天继续写的时候,发现自己都看不懂自己的代码了,又想了半天~

-------------------------------------------------------------------------------------------------------------------------------------

家里面真不是写代码的地,今天做这道题,中间出去n多次,到晚上9点才做出来。

罐子和硬币

题目详情:

有n个罐子,有k个硬币,每个罐子可以容纳任意数量的硬币。罐子是不透明的,起初你可以随机把这k个硬币任意放在罐子里。然后罐子被打乱顺序,你从外表无法区别罐子。最后罐子被编上号1-n。你有p次机会,每次你可以选择某个罐子,如果该罐子里有硬币,则你可以得到1个(你不可以知道该罐子里有多少硬币),如果该罐子是空的,你得不到任何硬币。你最终要得到至少c枚硬币,我们的问题是给定n,k,c,求出最少的p,存在一种你最初放硬币的方式,无论罐子如何被打乱顺序,你都能p次机会内获得至少c个硬币。

输入n,k,c (0 < n <=1000000, 0 < c <= k <=1000000)。

输出,最小的p值。

例如n = 3, k = 6, c = 4。 你可以把每个罐子放入两个硬币,这样得到4次机会可以得到4个硬币,输出4。

1.需要理解的地方:

(1)题目中的“罐子被打乱顺序”、“存在一种你最初放硬币的方式”,意思是我们知道有几个罐子中放了x个硬币,有几个罐子中放了y个硬币....,但是我们不知道那个罐子中放有x个硬币,哪个罐子中放有y个硬币...;

(2)题目的意思是:k个硬币在n个罐子中有多种分布方式,如第i种分布方式的所有情况中通过至少p次机会才能拿到c个硬币,现在需要你设计一种硬币分布方式,使得在这种方式下的p值是所有分布方式中最小的,并求得p。

2.我的做法如下:

所求的p由两部分构成:拿到硬币的情况和罐子为空的情况,显然拿到硬币的情况为c。题目是所有情况下至少p次机会才能拿到c个硬币,即其中的空罐子肯定会被试探到。

分情况讨论:

(1)当k<=n时,那么有k个罐子均放一个硬币时p最小,此时p=n-k + c, 此时罐子为空的情况次数为n-k,因为如果存在某个罐子中多于一个硬币,那么空罐子的数量将多于n-k。

(2)当k>n时,设k=a*n+b(其中a=k/n,b=k%n):

[1]当c<=a*n时(此种情况包含了k能被n整除的情况),显然此时p=c,硬币的放法为每个罐子先放a个硬币,剩余的b个硬币随便放,拿的时候从每个罐子中拿a个,不会出现空罐子的情况。

[2]当c>a*n时,我们分下面四种情况:

假设我们用n个罐子中的x=n1+1个来放硬币,那么已经不可避免的空操作次数为n-x,并且硬币的数量k和x的关系显然就两种关系:

div= k/n1,ys=k%n1, ys<=n1-1

关系(1)div>=ys , 关系 (2) div<ys

关系(1)时最小p对应情况用下图表示,即n1个罐子放div个硬币,一个放ys个硬币:



因为这时如果c<=ys*x【情况1】那么,那么这时没有额外的空操作,p=n-x+c

即使c>ys*x【情况2】,这时也仅有一次空操作,这不能通过减小x时来避免这次空操作,因为减小x值会增加空罐子数量,所以p=n-x+c+1。

关系(2)时对应情况用下图表示



如果c<=div*x【情况3】那么,那么这时没有额外的空操作,显然上图是一种p最小的情况,p=n-x+c

如果c>div*x 【情况4】,那么在得到前div*x个硬币的时候不会有空操作,在从ys-div(绿色部分)取c-div*x个硬币的时候肯定会出现空操作,这时用x个罐子放硬币的空操作的次数至少是x-(ys-div),即将绿色部分一个一个添加到其他罐子里,而且这是在x个罐子放硬币的情况下的p最小值,如下:



证:如果不是用上图的方法,那么如上图所示增加一个罐子的硬币数量,肯定会减少另一个罐子中的数量,导致减少的罐子里面的硬币小于阀值,增加空操作的次数,得证。

那么 【情况4】下能否可以通过减小所用罐子数量x来减少空操作的次数呢?

如果在x减小x-(ys-div)-1个内,即x的最小值为(ys-div)+1,能将情况4转换为情况1,2,3那么就可以通过减小所用罐子数量x来减少空操作的次数。

3.代码如下:

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
class Test {
public:
    static int pvalue (int   n,int   k,int   c){
		if(k<=n)
			return n-k+c;
		int kn=k-k%n;//kn=k/n*n
		if(kn>=c)return c;
		int n1=n-1;//初始值
		int x=n1+1;//n
		int ys=k%n1;
		int div=k/n1;//x
		if(div>=ys)
			if(c<=ys*x)//情况1
				return c;
			else //情况2
				return 1+c;
		else if(c<=div*x)//情况3
				return c;
		else{//情况4
			int xMin=ys-div;
			while(x>xMin)//减少x-(ys-div)-1个罐子内
				if(div>=ys)
					if(c<=ys*x)//转换为情况1
						return n-x+c;
					else //转换为情况2
						return n-x+1+c;
				else
					if(c<=div*x)//转换为情况3
						return n-x+c;
					else{
						--x;
						--n1;
						ys=k%n1;
						div=k/n1;
					}//else
			return n-xMin+c;//不能转换为其他三种情况
		}//else
    }
};
//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main(){   
    cout<<Test::pvalue(3,6,4)<<endl;   
	cout<<Test::pvalue(3,4,4)<<endl;  
	cout<<Test::pvalue(4,6,5)<<endl;
} 
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。


4.时间复杂度为n-(k%(n-1)-k/(n-1))==O(n)

其实这道题有O(1)的解法,来自“绿色夹克衫”

int m = k / n;
return c <= m * n ? c : c + n - k / (m + 1);
其中c<=m*n时,显然是p=c;

c>m*n时,我们得从某些罐子里面能拿出>=m+1个硬币时,才能最终拿到c个硬币,也就是说向每个罐子得试探至少m+1次为止或者中间罐子为空为止,出现空操作的次数就是其中放了小于m+1个硬币的罐子,那么最多能在多少个罐子中放m+1个硬币呢,显然是最多k/(m+1)个罐子中能有m+1个硬币,现在空操作的次数是n-k/(m+1)最少,所以是p=c+n-k/(m+1)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: