法國數學家梅齊亞克的砝碼問題

法國數學家梅齊亞克在他著名的《數字組合遊戲》(1962)中提出了一個問題:一位商人有一個重40磅的砝碼,一天不小心將砝碼摔成了四塊。後來商人稱得每塊的重量都是整磅數,而且發現這四塊碎片可以在天平上稱1至40磅之間的任意重量。請問這四塊碎片各重多少?
*問題分析與算法設計 寫道
      本題是上一題的發展。題目中給出的條件是“在天平上”,這意味着:同一砝碼既可以放在天平的左側,也可以放在天平的右側。若規定重物只能放在天平的左側,則當天平平衡時有:
      重物重量+左側砝碼重量總和=右側砝碼重量總和
      由此可得:
      重物重量=右側砝碼重量總和-左側砝碼重量總和
      編程時只要根據以上公式,使“右側砝碼重量總和-左側砝碼重量總和”可以表示1到40之間的全部重量即可。編程中要注意的是:怎樣採用一種簡單的方法來表示一個砝碼是在天平的左側還是在天平的右側,或是根本沒有使用。
      以下程序採用1、 -1和0分別表示上述三種情況,請注意理解。
C代碼
 // *程序說明與註釋   
#include<stdio.h>   
#include<math.h>   
int main()   
{   
int weight1,weight2,weight3,weight4,d1,d2,d3,d4,x,flag; /*flag:滿足題意的標記*/  
printf("The weight is broke up as following 4 pieces:");   
for(weight1=1;weight1<=40;weight1++) /*將40分解成4份*/  
for(weight2=weight1+1;weight2<=40-weight1;weight2++)   
for(weight3=weight2+1;weight3<=40-weight1-weight2;weight3++)   
if((weight4=40-weight1-weight2-weight3)>=weight3)   
{   
for(flag=1,x=1;x<41&&flag;x++) /*判斷可否稱出1~40之間的全部重量*/  
for(flag=0,d1=1;d1>-2;d1--) /*將重物放在天平的左邊*/  
for(d2=1;d2>-2&&!flag;d2--) /*1:砝碼在天平右邊*/  
for(d3=1;d3>-2&&!flag;d3--) /*0:不用該砝碼*/  
for(d4=1;d4>-2&&!flag;d4--) /*-1:砝碼在天平的左邊*/  
if(x==weight1*d1+weight2*d2+weight3*d3+weight4*d4)   
flag=1;   
if(flag) printf("%d %d %d %d/n",weight1,weight2,weight3,weight4);   
flag=0;   
}   
}   
  
// *運行結果   
The weight is broke up as following 4 pieces: 1 3 9 27
百度的解釋 寫道
      能夠算出四塊分別是27,9,3,1.因爲任何一個1-40.都能寫成A*3^3+B*3^2+C*3^1+D*3^0.構造一個三進制的數.則1-40 改爲三進制數表示爲(ABCD)3.A,B,C,D表示各個數位上的數,其取值範圍爲{0,1,2}.當四個數位上的數值都是0或者1時,用對應的砝碼放在同一個盤稱就行了,當某個數位上的數值爲2的時候,則把它前一位的砝碼和其他需要的砝碼放一個盤,這一位的砝碼和重物放一個盤就能稱.如需要稱33磅, 他用三進制的數表示爲(1020)3,將27磅和9磅的砝碼放左盤,3磅的砝碼放右盤和重物放一起就能稱量.另外,如前一位的數值爲1或者2的時候,應該再往前推一位,如15磅,寫成三進制的數爲(0120),應將一個27磅的砝碼放左盤,右盤放9磅和3磅的砝碼,加上重物就能稱量了.
      這是利用了3的N次方,通過加減運算能表示仍一個正整數的原理.爲什麼呢,剛纔構造的三進制的數是能表示任一個正整數的,當進行減法運算的時候,就可以表示成該數位上的數爲-1.也就意味着,他的前一個數位應該加1,而這個數位的數值變成了2.這樣3的N次方加減後,得到的數,能夠表示成三進制數的形式,所以也就能表示任一個正整數了
寫道
      定理: 由m個數構成的由小到大排列的數列{a(1),a(2),...a(m)},設A(k)=∑a(i), 其中i從1到k, 則
a(1) = 1且a(j+1) <= 2A(j) +1, j取1,2,..,m-1 (1式)是該數列作爲砝碼序列可稱量{0,1,..,Am}範圍內的任意整數重量的充要條件。特別的,上式取等號時,該序列是唯一可能的砝碼序列,並且有a(j) = 3^(j-1), 對於j=1,2,..,m

      推論: 重量爲n的物體要分成m份重量爲整數的物體的序列{a(1),a(2),..a(m)},設M=∑3^(i-1),其中i從1到m,則有三種情況:
      1) M<n, 無解;
      2) M=n,有唯一的解 a(j)=3^(j-1), j=1,2,..m;
      3) M>n,可能有多組解,解爲滿足(1式)並且∑a(i)=n,其中i從1到m,的所有整數序列。

      定理的證明:
      (充分性)
      用數歸法:
      當i=1的時候,a(i)=1顯然成立;
假設i=k的時候定理充分性成立,即用滿足(1)式的前k個砝碼可以稱量的重量W(k)爲滿足0<=W(k)<=A(k)的所有整數,則i=k+1時,應可以稱量W(k+1),應爲0<=W(k+1)<=A(k+1)範圍內的所有整數。分段討論如下:
      (a)對於0<=W(k+1)<=A(k),顯然可以由前k個砝碼稱量;
      (b) 對於A(k)<W(k+1)<=a(k+1), 由假設0<=W(k)<=A(k),交換左右盤的砝碼,可以產生配合砝碼a(k+1)使用的負砝碼爲W(k)' 可以是滿足-A(k)<=W(k)'<=0的所有整數。與大砝碼a(k+1)一起使用可以得到a(k+1)+W(k)' ,一定可以稱量某段連續範圍的所有整數,因爲a(k+1) <=2A(k)+1, 所以a(k+1)-A(k) <= A(k)+1,因此a(k+1)+W(k)'產生的下限爲a(k+1)-A(k),上限爲a(k+1),所以可以稱量 A(k)<W(k+1)<=a(k+1)內的所有W(k+1);
      (c)對於a(k+1)<=W(k+1)<=A(k+1),與(b)同理可以得到稱量的上下限分別爲:a(k+1)+A(k) = A(k+1)和a(k+1);
因此當i=k+1的時候定理充分性也成立,由數歸法知定理充分性成立。

      (必要性)
i=1時,顯然必須有總量爲1的砝碼;
i>1時,反證之,如果存在某個K,使得(1)不成立,即2A(k)+1<a(k+1),則重量A(k)+1既不能用前面的k-1個砝碼稱重,又因爲a(k+1)-A(k)>A(k)+1而不能用a(k+1)配合着稱重。所以矛盾,因此必要性成立。

推論也可以用數歸法簡單的證明,這裏我就不證了,打字太累了:)

根據以上的定理和推論,可以很容易的求出對於重量爲任意的n的物體,用m個砝碼可以稱出來的砝碼的方案。當n=∑3^(i-1), i從1到m的時候,有唯一解a(i)=3^(i-1),可以改寫成a(i)=2(∑aj)+1,其中j從1到i-1,一個循環就直接輸出了;當n> ∑3^(i-1)的時候無解;當n<∑3^(i-1)的時候只要根據式(1)並保證∑a(i)=n搜索就可以了。可以遞歸的搜索求解。具體我就不寫程序了。
http://www.zaoxue.com/article/tech-25044.htm
csdn給出的方法 寫道
1 3 9 27 : 後項是前面各項和的2倍再加1 
3 = 2×1 + 1 
9 = 2×(1+3) + 1 
27 = 2*(1+3+9 )+ 1 

和那個一個金項鍊分成幾塊,用來支付工資,要求分割最少,可以滿足各種需求的題目是一樣的

*******************************************************
就是3進制數,任何一個數寫成3進制,然後等價的轉換成在-1,0,1上面的3進製表示, 
然後就可以知道如何用這幾個數來稱了

*******************************************************
//砝碼稱重dp思想 
#include<stdio.h> 
int main() 

int num[6]={1,2,3,5,10,20};//砝碼重量序列 
int a[6];//6個砝碼的個數 
bool vist[1000]={false};//訪問標誌位 
int no[1000];//no[0]爲不同重量個數,no[j]--第j種重量1<=j<=no[0] 
int i,j,k,total;//total:目前稱出的重量 
for(i=0;i<6;i++) 
scanf("%d",&a[i]);//輸入6個砝碼的個數 
no[0]=1;no[1]=0;//稱出第一種質量0 
for(i=0;i<6;i++)//階段:分析第i種砝碼 
for(j=0;j<no[0];j++)//狀態:枚舉現有的不同重量 
for(k=0;k<a[i];k++)//決策:在現有重量的基礎上放k塊第i種砝碼 

total=no[j]+k*num[i];//產生重量 
if(!vist[total]) 

vist[total]=true;//若該重量未產生過,則設訪問標誌 
no[0]++; 
no[no[0]]=total; 
// printf("%d/n",total); 



printf("%d/n",no[0]-1); 
return 0; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章