一、概述
现在有1000杯水,其中有一杯有毒。你有若干只猪,猪喝完水会在15分钟内挂掉。一只猪一次可以喝许多杯水。你有60分钟,请问最少使用多少只猪能够确定那杯有毒的水?
扩展:
现在有s杯水,其中有一杯有毒。你有若干只猪,猪喝完水会在x分钟内挂掉。一只猪一次可以喝许多杯水。你有y分钟,请问最少使用多少只猪能够确定那杯有毒的水?
二、分析
好鸡儿难啊。这是最近总在群里或者论坛里看到的一道题。冠名为微软2012面试题等等。今天在LeetCode的每日一题看到了。本来以为简单,但是做了好几个小时才做出来。
我把我走过的坑一点一点说一下。
首先,咱们要试出两个样例:
第一个:有25杯水,最多做4次试验时,猪有2只;有26杯水,最多做4次试验时,猪有3只。
第二个:有125杯水,最多做4次试验时,猪有3只;有126杯水,最多做4次试验时,猪有4只。
先看第一个样例。
第一个样例告诉我们,猪有2只且最多做4次试验时,最多辨认出25杯水。
在这里我就卡了快一个小时。啥子?俩猪能辨别出25杯水?咋搞的?
这样搞的:
第一次试验:
将1,2,3,4,5喂给一号猪。
将6,7,8,9,5喂给二号猪。
若一号猪死球了,那么说明毒水在1,2,3,4中。
若二号猪死球了,那么说明毒水在6,7,8,9中。
以上两种情况,还有三次试验,还剩一只猪,一瓶一瓶喂,就能确定四杯水哪杯有毒。
若俩猪都死球了,那么说明毒水是5号。
若没有猪死,那么排除了9杯水。
第二次试验:
将10,11,12,13喂给一号猪。
将14,15,16,13喂给二号猪。
若一号猪死球了,那么说明毒水在10,11,12中。
若二号猪死球了,那么说明毒水在14,15,16中。
以上两种情况,还有两次试验,还剩一只猪,一瓶一瓶喂,就能确定三杯水哪杯有毒。
若俩猪都死球了,那么说明毒水是13号。
若没有猪死,那么排除了7杯水。
第三次试验同理。可以排除5杯水。
最后一次试验,由于必定剩两只活猪,那么可以排除2^2=4杯水。
所以9+7+5+4=25杯。
主要不容易想到的就是猪很宝贵,能不死就不死。不要用二分什么的。
另外如何确定第一次试验要喂给每只猪几杯水呢?
喂5杯。这个5是怎么来的?4+1。其中,4是有一只猪死了,剩下试验能辨别出的最大杯数(剩一只猪,剩三次试验),1是根据猪死了的状态直接判断哪杯是毒水。好。这个是核心。下面拓展到第二个样例。
第二个样例告诉我们,猪有三只,且最多做四次试验时,最多辨认125杯水。哦豁。效率提高了这么多。咋办到的?这样搞:
第一次试验:
将1,2,3,···,14,15,16,49,50,51,52,57,58,59,60,61喂给一号猪。
将17,18,19,···,30,31,32,49,50,51,52,53,54,55,56,61喂给二号猪。
将33,34,35,···,46,47,48,53,54,55,56,57,58,59,60,61喂给三号猪。
若一号猪死球了,那么说明毒水在1,2,3,···,14,15,16中。
若二号猪死球了,那么说明毒水在17,18,19,···,30,31,32中。
若三号猪死球了,那么说明毒水在33,34,35,···,46,47,48中。
以上三种情况,还有三次试验,还有两只猪,我们来看第一个样例,两只猪三次试验,最多辨认7+5+4=16杯水。所以给每只猪单独喝的是16杯水。
若一号猪和二号猪死球了,那么说明毒水在49,50,51,52中。
若二号猪和三号猪死球了,那么说明毒水在53,54,55,56中。
若一号猪和三号猪死球了,那么说明毒水在57,58,59,60中。
以上三种情况,还有三次试验,还有一只猪。一杯一杯喂,可以辨认4杯水。所以两只猪喝同样的水的是4杯。
若三只猪都死球了,61号是毒水。
若没有猪死球,排除61杯水
从以上可以看出,第一次排除的61,是48+12+1得到的。
其中48,是16*3得到的。其中16,是上一个样例的7+5+4得到的。
其中12比较难以得到,这个是最难想的。大家一般都会想到这种形式:
即把49喂给一号和二号猪,50喂给二号三号,51喂给一号三号。看猪死的状态直接确定哪杯水是毒水。但实际上,这浪费了剩余一只猪的鉴定价值。按上面这种形式,如果50号是毒水,那么直接确定。但如果我们把50和51都喂给一号二号呢?那么当一号二号死球了,我们可以确定50或51有毒,之后用三号就能确定哪杯有毒了。这个是十分重要的。
那么12如何计算出来呢?用组合数。在三只猪中选二只死球,一只活着,共有三种选法。剩下一只猪,三次试验。可以确定4杯水。因此三种选法乘以四杯水就得到了12。
这样我们来看,本题有两个关键变量:
其一,每次试验能够排除的水的最大杯数,记为f;
其二,所有试验做完了能够排除水的最大杯数,记为F。
设f(n,p)为还有n次试验,还有p只猪,本次实验能够排除的水的杯数。例如,f(4,2)=9,f(4,3)=61。
注意,f(1,p)=2^p,f(n,1)=1,f(1,1)=2。
设F(n,p)为还有n次试验,还有p只猪,所有试验做完了最多能够排除水的杯数。例如,F(4,2)=25,F(4,3)=125。
那么我们可以归纳出F和f的计算公式如下:
f
(
n
,
p
)
=
1
+
∑
i
=
1
p
−
1
C
p
i
F
(
n
−
1
,
p
−
i
)
F
(
n
,
p
)
=
∑
i
=
1
n
f
(
i
,
p
)
f(n,p)=1+\sum_{i=1}^{p-1}C_{p}^{i}F(n-1,p-i)\\ F(n,p)=\sum_{i=1}^{n}f(i,p)
f(n,p)=1+i=1∑p−1CpiF(n−1,p−i)F(n,p)=i=1∑nf(i,p)
三、总结
好麻烦啊!好复杂啊!那个2只猪试验25杯水我自己想的怀疑人生了= =
PS:
代码如下:
class Solution {
int C(int a,int b)
{
long long m1=1,c=b+1;
while(c<=a)
{
m1*=c;
++c;
}
int m2=1,d=1;
while(d<=(a-b))
{
m1/=d;
d++;
}
return m1;
}
int f(int n,int p)
{
if(n==1)
return pow(2,p);
if(p==1)
return 1;
int res=0,pp=1;
while(pp<=p-1)
{
res+=C(p,pp)*F(n-1,p-pp);
++pp;
}
return res+1;
}
int F(int n,int p)
{
int res=0;
for(int i=n;i>0;--i)
{
int t=f(i,p);
res+=t;
}
return res;
}
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
if(buckets==1)
return 0;
int n=minutesToTest/minutesToDie;
int res=1;
int all=F(n,1);
while(all<buckets)
{
++res;
all=F(n,res);
}
return res;
}
};