日本數學家橋本吉彥教授於1973年10月在我國山東舉行的中日美三國數學教育研討會上向與會者提出以下填數趣題:
把1,2, . . . 9這9個數填入下列算式的9個方格中(數字不得重複),使下列等式成立。
口 口 口
——— + ——— = ———
口口 口口 口口
橋本教授當即給出了一個解答,這一填數趣題的解是否唯一?如果不唯一究竟有多少個解?
d試求出所有解答(等式左邊兩邊分數交換次序只算一個解答);
答案:10
1/26+5/78=4/39
1/32+5/96=7/84
1/32+7/96=5/48
1/78+4/39=6/52
1/96+7/48=5/32
2/68+9/51=7/34
2/68+9/34=5/17
4/56+7/98=3/21
5/26+9/78=4/13
6/34+8/51=9/27
解析1:因爲1,2,3,4,5,6,7,8,9這九個數字不得重複,也就1~9是分別且出現一次,可以用全排列的算法來解答,因爲分別且出現一次是quan全排列算法的前提。先定義一個數組c(c[9]),分別存放這九個數,算式中一共有九個空,所以給每個空都附上數組的下標,如下圖所示:
題目中說等式左邊兩邊分數交換次序只算一個解答,也就是說3+7=10、7+3=10,這兩種情況算一種有效情況,我們不妨讓等式左邊的分數用c[0]來代替,右邊的分數用c[3]來代替,c[0]和c[3]中要麼c[0]大,要麼c[3]大,只需要c[0]<c[3],這樣的話就避開了等式左邊兩邊分數交換次序只算一個解答的情況了。我們可以把等式中的9個數看作六位數,並把除法表達式轉換爲成法表達式,這樣就可以避免小數的情況了:
int A = c[0];
int B = c[1]*10+c[2];
int C = c[3];
int D = c[4]*10+c[5];
int E = c[6];
int F = c[7]*10+c[8];
除法形式:A*D*F+C*B*F==E*B*D
代碼如下:(大約15ms左右)
public class 分數全排列法
{
private static int cnt = 0;
public static void Swap(int[] c,int i,int j)
{
int tmp = c[i];
c[i] = c[j];
c[j] = tmp;
}
public static void AllPermutation(int[] c,int start) //全排列算法
{
if(start==c.length-1)
{ //例如 3+7=10 、 7+3=10,兩者算一種情況
if(c[0]<c[3]) //避免等式左邊兩數交換,只取一種情況即可
{
int A = c[0];
int B = c[1]*10+c[2];
int C = c[3];
int D = c[4]*10+c[5];
int E = c[6];
int F = c[7]*10+c[8];
if(A*D*F+C*B*F==E*B*D) //把除法表達式轉換爲乘法表達式
{
cnt++; //統計個數
System.out.println(A+"/"+B+"+"+C+"/"+D+"="+E+"/"+F);
}
}
}
else
{
for(int i=start,t=c.length;i<t;++i)
{
Swap(c,i,start);
AllPermutation(c,start+1);
Swap(c,start,i);
}
}
}
public static void main(String[] args)
{
long start = System.currentTimeMillis(); //與本題無關,計算程序耗時的
int[] c = {1,2,3,4,5,6,7,8,9};
AllPermutation(c,0); //全排列算法
System.out.println(cnt); //輸出符合條件的個數
long last = System.currentTimeMillis() - start; //與本題無關,計算程序耗時的
System.out.println("耗時:"+last+"ms"); //與本題無關,計算程序耗時的
}
}
運行結果:
1/26+5/78=4/39
1/32+5/96=7/84
1/32+7/96=5/48
1/78+4/39=6/52
1/96+7/48=5/32
2/68+9/51=7/34
2/68+9/34=5/17
4/56+7/98=3/21
5/26+9/78=4/13
6/34+8/51=9/27
10
耗時:10ms
解析2:解題思路和解析1的思路大體相同,但是算法不同,該算法是回溯算法,回溯算法是有一套模板的,解析1中的全排列算法也有一套把模板,都是直接套用即可,其中數組的下標起始位置不同,如下圖所示:
int A = a[1];
int B = a[2]*10+a[3];
int C = a[4];
int D = a[5]*10+a[6];
int E = a[7];
int F = a[8]*10+a[9];
代碼如下:(大約120ms左右)
import java.util.Scanner;
public class 分數回溯法
{
public static void main(String[] args)
{
long start = System.currentTimeMillis(); //與本題無關,計算程序耗時的
int n = 9; //總共n個數
int[] a = new int[n+3];
int i = 1; //i代表第幾個數,從第一個數1開始
a[i] = 1; //最小起始值
int g;
int cnt = 0; //計數
while(true)
{
g = 1;
for(int k=i-1;k>=1;--k)
{
if(a[i]==a[k]) //第一個約束條件,數字不能相同
{
g = 0;
break;
}
}
if(g!=0&&i==n&&a[1]<a[4]) //第二個約束條件,必須到達9個數字,並且等號左邊兩個數交換算一種情況
{
int A = a[1];
int B = a[2]*10+a[3];
int C = a[4];
int D = a[5]*10+a[6];
int E = a[7];
int F = a[8]*10+a[9];
if(A*D*F+C*B*F==E*B*D) //把除法表達式轉換爲乘法表達式
{
cnt++;
System.out.println(A+"/"+B+"+"+C+"/"+D+"="+E+"/"+F);
}
}
if(i<n&&g!=0) //還沒到第9個數
{
++i; //下一個數
a[i] = 1; //讓這個數從最小值1開始
continue;
}
while(a[i]==n&&i>1) //向前回溯
{
--i;
}
if(a[i]==n&&i==1)
{
break;
}
else
{
a[i] = a[i] + 1;
}
}
System.out.println(cnt);
long last = System.currentTimeMillis() - start; //與本題無關,計算程序耗時的
System.out.println("耗時:"+last+"ms"); //與本題無關,計算程序耗時的
}
}
運行結果:
1/26+5/78=4/39
1/32+5/96=7/84
1/32+7/96=5/48
1/78+4/39=6/52
1/96+7/48=5/32
2/68+9/34=5/17
2/68+9/51=7/34
4/56+7/98=3/21
5/26+9/78=4/13
6/34+8/51=9/27
10
耗時:115ms