日本数学家桥本吉彦教授于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