百钱买百鸡经典问题

来源:互联网 发布:mac能玩国服lol吗 编辑:程序博客网 时间:2024/06/18 07:44

有个商人不小心把40磅的砝码摔碎了,摔成了4块,欲哭无泪时发现这4块砝码恰好可以组合成1-40的任意重量,求这4块碎砝码的质量。(砝码系数可以取-1,0,1)

答案:1,3,9,27

数学依据:

数学上的命题是这样的(以下用符号^表示指数。如3^2表示3的2次方):
假设有序列An= {3^0, 3^1, 3^2,...,3^n},
要证明可以用该序列中的1~n个元素通过加减法组合出Sum(An)中的任意整数。
可以用数学归纳法获得证明。
证明:
当n=1,n=2时,可以用手工方法检验该命题是正确的。

假设当n=K时该命题成立,即对Sum(AK)中的任意整数p,都可以找到这样的纯加减算法P(AK),使得P(AK)=p。

那么当n=K+1时...
对于Sum(AK)中的任意整数P,我们可以用原先的算法P(AK)生成。
对于对于Sum(AK)~Sum(A[K+1])之间的整数,我们把它分成2段:
第一段:Sum(AK)+1 ,..., 3^(K+1)-1,
第二段: 3^(K+1), 3^(K+1)+1, ..., 3^(K+1)+Sum(AK)。
对于第2段的数字q,可以将它拆分成q=3^(K+1)+p,我们可以用Q(A[K+1])=3^(K+1)+P(AK)这样的方法导出算法,因此也是成立的。
然后对于第1段数据进行分析。
高中的数学知识告诉我们,对于公比为q的等比数列{a,a*q,a*q^2,...,a*q^n},其求和公式为
S= a*(1-q^(n+1))/(1-q)。
在本题中,Sum(AK)+1= (3^(K+1)-1)/2 + 1= (3^(K+1)+1)/2 > (3^(K+1)-1)/2。
所以对第一段中的数字q,可以将它拆分为q=3^(K+1)-p,且0<=p<=Sum(AK)。 我们可以用 Q(A[K+1])=3^(K+1)-P(AK)这样的方法导出算法。
因此当n=K+1的时候该命题也是成立的。

另外:对于任意序列AN= {a1,a2,...,aN},如果能利用某种加减算法拼接出1~Sum(AN)中的所有整数,那么考虑到加,减两种可能的算法,如果a[N+1]<=Sum(AN)+1,该命题仍然是成立的。而3恰恰是分界线。

java代码(遍历所有可能):

class mathDemo {
mathDemo() {

n1 = 0;
n2 = 0;
n3 = 0;
n4 = 0;
}

boolean tryit(int a,int sum)
{
return(a==sum);

}

boolean tryit(int a,int b,int sum)
{
return(a+b==sum  ||
-a+b==sum ||
a-b==sum);


}

boolean tryit(int a,int b,int c,int sum)
{
return(a+b+c==sum||
a+b-c==sum||
a-b+c==sum||
a-b-c==sum||
-a+b+c==sum||
-a+b-c==sum||
-a-b+c==sum);

}


boolean tryit(int a,int b,int c,int d,int sum)
{


return(
a+b+c+d==sum||
a+b+c-d==sum||
a+b-c+d==sum||
a+b-c-d==sum||
a-b+c+d==sum||
a-b+c-d==sum||
a-b-c+d==sum||
a-b-c-d==sum||
-a+b+c-d==sum||
         -a+b-c+d==sum||
-a+b-c-d==sum||
-a-b+c+d==sum||
-a-b+c-d==sum||
-a-b-c+d==sum||
-a-b-c-d==sum  
);


}

boolean check(int a,int b,int c,int d)
{
boolean flag1=true;
for(int i=1;i<41&&flag1==true;i++)
{
System.out.println("check:"+i);
flag1=(
tryit(a,i)||
tryit(b,i)||
tryit(c,i)||
tryit(d,i)||
tryit(a,b,i)||
tryit(a,c,i)||
tryit(a,d,i)||
tryit(b,c,i)||
tryit(b,d,i)||
tryit(c,d,i)||
tryit(a,b,c,i)||
tryit(a,b,d,i)||
tryit(a,c,d,i)||
tryit(b,c,d,i)||
tryit(a,b,c,d,i)
);
};
System.out.println("the num can do it: "+flag1);
System.out.println();
return flag1;
}

void xunhuan() {
while (n1 < 41 & (flag == false)) {

n2 = 1;
n1++;
while (n2 < 41 - n1 & (flag == false)) {

n3 = 1;
n2++;
while (n3 < 41 - n2 - n1 & (flag == false)) {
n3++;
n4 = 40 - n1 - n2 - n3;
System.out.print("use the numb:" + n1 + " " + n2 + " " + n3
+ " " + n4 + " to check");

flag = check(n1, n2, n3, n4);

}
}
}
}

public static void main(String[] agr) {

mathDemo a = new mathDemo();
a.xunhuan();
if (a.flag == true) {
System.out.println(a.n1);
System.out.println(a.n2);
System.out.println(a.n3);
System.out.println(a.n4);
} else {
System.out.println();
System.out.println(a.flag);
System.out.println("can't find numbs...");
}

}

int n1, n2, n3, n4;

boolean flag = false;
}
/////////////////////////////////////////////////////////////////////

上面的方法太笨,而且耗时太多。

下面的想法不错:

组成1-n的任意砝码重量(整数),要求所用的钱币张数最少,那么怎么组合?


关键:
--------------------------------
砝码的组合,可以选择性的放在左右两边

那么可以是加法,也可以是减法
----------------------------------

因为可以用减法
于是我们得到:

假设已经实现了1-N个数字
那么下一个要取的数A的左边和右边都可以"实现"N个数字
左边的实现方法也就是用A-已经实现的1-n
右边的实现方法也就是用A+已经实现的1-n

可能这样说有点郁闷

例如

因为肯定需要一个数字
所以必须实现:1

因为实现了1,那么下一个要取的数字B的左边可以B-1,右边B+1,那么都可以实现一个数字

第二个数的左边挨着1实现一个数字就是:2

那么

第二个要取的数字就是:3
那么右边实现一个数字也就是:4
这个数字的左右两边的1个数字是因为1已经实现而实现的

好,现在我们已经实现了:1.2.3.4  四个数字

同理,
第三个要取的数的左边和右边都可以实现4个数字
那么左边接着4,就是5.6.7.8这四个数
不难看出

第三个数字是9
9的右边我们还可以实现:10.11.12.13这4个数字

好,我们已经实现了1.2.3.4......11.12.13这13个数字
那么第四个数字的左边也可以实现13个数字:14.15.16........25.26

第四个数字就是:27
27的右边再用+法又可以实现:28.29.30........38.39.40


好了,观察一下不难发现

公式为:

(0+1)*2+1=3
(1+3)*2+1=9
(1+3+9)+1=8
(1+3+9+27)*2+1=81

大家也许会发现,和楼上的公式差不多,也是前面的总和+1
但是因为可以用-法,所以每个数的范围扩大为2倍

 4个数,只可互相加减...要把数据段中1-N中的数都实现,那么N最大为40....