如图所示,这是一款基于android的24点游戏,输入四个整数(1--13)之后,点击计算之后,计算机就可以自动给出所有可能的答案。
24点是把4个整数(一般是正整数)通过加减乘除运算,使最后的计算结果是24的一个数学游戏,可以考验人的智力和数学敏感性。通常是使用扑克牌来进行游 戏的,一副牌中抽去大小王后还剩下52张(如果初练也可只用1~10这40张牌),任意抽取4张牌(称为牌组),用加、减、乘、除 (可加括号)把牌面上的数算成24。每张牌必须且只能用一次,如抽出的牌是3、8、8、9,那么算式为(9—8)×8×3或3×8÷(9—8)或(9— 8÷8)×3等。
它始于何年何月已无从考究,但它以自己独具的数学魅力和丰富的内涵正逐渐被越来越多的人们所接受。这种游戏方式简单易学,能健脑益智,是一项极为有益的活动。
对于24点的计算,可以有如下的一些技巧:
1.利用3×8=24、4×6=24 、12×2=24求解。
把牌面上的四个数想办法凑成3和8、4和6、12和2再相乘求解。如3、3、6、10可组成(10-6÷3)×3=24或(10-3-3)×6=24。又如2、3、3、7可组成(7+3-2)×3=24等。实践证明,这种方法是利用率最大、命中率最高的一种方法。
2.利用0、1的运算特性求解。
如3、4、4、8可组成3×8+4—4=24等。又如4、5、J、K可组成11×(5—4)+13=24等。
3.在有解的牌组中,用得最为广泛的是以下六种解法:(我们用a、b、c、d表示牌面上的四个数)
①(a—b)×(c+d)
如(10—4)×(2+2)=24等。
②(a+b)÷c×d
如(10+2)÷2×4=24等。
③(a-b÷c)×d
如(3—2÷2)×12=24等。
④(a+b-c)×d
如(9+5—2)×2=24等。
⑤a×b+c—d
如11×3+l—10=24等。
⑥(a-b)×c+d
如(4—l)×6+6=24等。
此外,关于更多的24点的扩展(主要是基于组合数学+概率的一些延伸,我会在最后补充)
下面,我来给出HDOJ 1427(杭电OJ)中关于此问题的描述,并给出并品析其AI算法的精髓。
Problem Description
速算24点相信绝大多数人都玩过。就是随机给你四张牌,包括A(1),2,3,4,5,6,7,8,9,10,J(11),Q(12),K(13)。要求只 用'+','-','*','/'运算符以及括号改变运算顺序,使得最终运算结果为24(每个数必须且仅能用一次)。游戏很简单,但遇到无解的情况往往让 人很郁闷。你的任务就是针对每一组随机产生的四张牌,判断是否有解。我们另外规定,整个计算过程中都不能出现小数(吴昊评注:这一个条件并不是必要的,尤其在于一些只有唯一解的24点解法中)。
Input
每组输入数据占一行,给定四张牌。
Output
每一组输入数据对应一行输出。如果有解则输出"Yes",无解则输出"No"。
以上,我给出了原问题,可以说,和各地的麻将的玩法都不同一回事,24点也有各种玩法,其AI代码也是大同小异,究其精髓,还是最最不同的搜索,不过,搜索的过程中,要注意,按照顺序搜索,先搜两张牌的结果,再混为三张牌,最后是四张牌,不要用暴力!这样的AI木有效率!
关 于源代码的品析我都已经加入到注释上了,我觉得Cal函数起到了计算器的作用,而CalThree和CalTwo分的两种情况也很全面。至于 CalFour,则起到一个总括的作用(源代码在四个数无法组成24时编译器会报错,但是我反复检验源码认为木有问题,求DEBUG)。
2
3 #include<stdlib.h>
4
5 #include< string.h> // 开字符串库,处理输入输出
6
7
8
9 int num[ 4];
10
11
12
13 int Cal( int a, int b, int n) // 两个数的加减乘除过程
14
15 {
16
17 switch(n)
18
19 {
20
21 case 0: return a+b;
22
23 case 1: return a*b;
24
25 case 2: if(a>b) return a-b;
26
27 else return b-a;
28
29 case 3: if(a>=b&&b!= 0&&a%b== 0) return a/b;
30
31 else if(a<b&&a!=b&&b%a== 0) return b/a;
32
33 else return - 1; // 如果没有答案,就返回-1
34
35 }
36
37 return - 1; // 这也是为了增强鲁棒性
38
39 }
40
41
42
43 bool CalThree( int a, int b, int as)
44
45 {
46
47 int i,j,temp;
48
49 int k= 0,re[ 2]; // 再抓起最后的两张牌
50
51 for(i= 0;i< 4;i++)
52
53 {
54
55 if((i==a)||(i==b)) continue;
56
57 re[k++]=i; // 数组里面自增是个不错的习惯,如果每次增加的时机都不确定的话
58
59 }
60
61 for(i= 0;i< 4;i++) // 同理,再对四种运算进行处理
62
63 {
64
65 temp=Cal( as,num[re[ 0]],i);
66
67 if(temp==- 1) continue;
68
69 for(j= 0;j< 4;j++) // 再取一张牌,看是否匹配24
70
71 {
72
73 if(Cal(temp,num[re[ 1]],j)== 24)
74
75 return true;
76
77 }
78
79 }
80
81 for(i= 0;i< 4;i++) // 也有可能是先算第二张再算第一张
82
83 {
84
85 temp=Cal( as,num[re[ 1]],i);
86
87 if(temp==- 1) continue;
88
89 for(j= 0;j< 4;j++)
90
91 {
92
93 if(Cal(temp,num[re[ 0]],j)== 24)
94
95 return true;
96
97 }
98
99 }
100
101 return false;
102
103 }
104
105
106
107 bool CalTwo( int a, int b, int as)
108
109 {
110
111 int i,j,k= 0,re[ 2];
112
113 int temp;
114
115 for(i= 0;i< 4;i++)
116
117 {
118
119 if(i==a||i==b) continue;
120
121 re[k++]=i;
122
123 }
124
125 for(i= 0;i< 4;i++)
126
127 {
128
129 temp=Cal(num[re[ 0]],num[re[ 1]],i);
130
131 if(temp==- 1) continue;
132
133 for(j= 0;j< 4;j++) // 同上,测试四种运算
134
135 {
136
137 if(Cal( as,temp,j)== 24)
138
139 return true;
140
141 }
142
143 }
144
145 return false;
146
147 }
148
149
150
151 bool CalFour( int n)
152
153 {
154
155 int i,j,temp;
156
157 bool flag;
158
159 for(i= 0;i< 4;i++) // 再抽取一张牌,这张牌和以上的牌应该不同
160
161 {
162
163 if(i==n) continue;
164
165 for(j= 0;j< 4;j++) // 对于以上两张牌,利用不同的运算符号进行计算
166
167 {
168
169 temp=Cal(num[n],num[i],j);
170
171 if(temp==- 1) continue;
172
173 flag=CalThree(n,i,temp); // 第一类情况,第三张与前面两张相混合
174
175 if(flag) return true;
176
177 flag=CalTwo(n,i,temp); // 第二类情况,第三张与前面两张不互相混合
178
179 if(flag) return true;
180
181 }
182
183 }
184
185 return false;
186
187 }
188
189
190
191 int main()
192
193 {
194
195 int i;
196
197 char ch[ 10][ 5]; // 数组开大一点,可以增强鲁棒性,有可能保存几个空格的情况
198
199 bool ans;
200
201 // freopen(“f:\\1427.in”,”r”,stdin);
202
203 // freopen(“f:\\1427.txt”,”w”,stdout);
204
205 while(scanf( " %s%s%s%s ",ch[ 0],ch[ 1],ch[ 2],ch[ 3]) != EOF)
206
207 {
208
209 for(i= 0;i< 4; i++) // 将四张输入的牌取出
210
211 {
212
213 if(ch[i][ 0]== ' A ')
214
215 num[i] = 1;
216
217 else if(ch[i][ 0] == ' J ')
218
219 num[i] = 11;
220
221 else if(ch[i][ 0] == ' Q ')
222
223 num[i] = 12;
224
225 else if(ch[i][ 0] == ' K ')
226
227 num[i] = 13;
228
229 else if(ch[i][ 0] == ' 1 ' && strlen(ch[i])== 2) // 处理两个字符的情况
230
231 num[i] = 10;
232
233 else
234
235 num[i] =ch[i][ 0]- 48; // 将ASC码转为整数
236
237 }
238
239 ans = false;
240
241 for(i= 0;i< 4; i++)
242
243 {
244
245 ans =CalFour(i); // 判定过程,i表示先抽取一张牌
246
247 if(ans) // 由于这里只需要判定是否可以组成24点,故加此判定可以起到剪枝的效果
248
249 break;
250
251 }
252
253 if(ans) puts( " Yes ");
254
255 else puts( " No ");
256
257 }
258
259 return 0;
260
261 }
关于24点的一些基于组合数学的拓展:
较有难度的24点
虽然大多数24点存在很多解法,有相当一部分数字组合只存在唯一的解法。这种组合往往较有难度,也较为有趣。这里总结一些常见的组合。
分数运算
虽然给出4个数字都是整数,中间步骤中有时会出现分数。这种4个数字的组合往往较有难度。一个经典的例子是1,5,5,5,其解答为5 × (5 − 1 ÷ 5) = 24;另外 一个例子是3,3,8,8,其解答为8 ÷ (3 - 8 ÷ 3) = 24。因为后者用到了两次除法,其解法比较难以想到。另外一些类似的组合为:
数字组合 |
解法 |
数字组合 |
解法 |
2, 4, 10, 10 |
(2 + 4 ÷ 10) × 10 |
2, 5, 5, 10 |
(5 − 2 ÷ 10) × 5 |
2, 7, 7, 10 |
(2 + 10 ÷ 7) × 7 |
3, 3, 7, 7 |
(3 + 3 ÷ 7) × 7 |
4, 4, 7, 7 |
(4 − 4 ÷ 7) × 7 |
2, 2, 11, 11 |
(2 + 2 ÷ 11) × 11 |
2, 2, 13, 13 |
(2 − 2 ÷ 13) × 13 |
1, 3, 4, 6 |
6 ÷ (1 − 3 ÷ 4) |
2, 3, 5, 12 |
12 ÷ (3 − 5 ÷ 2) |
1, 8, 12, 12 |
12 ÷ (12 ÷ 8 - 1) |
大数/奇数运算
大 多数组合中,中间步骤只会涉及到一些较小的数字(≤32)。但是有些组合中会涉及到一些较大数字,这些组合通常较有难度。比如4、4、10、10 的解法为(10 × 10 − 4) ÷ 4 = 24,5、6、6、9的解法为6 × 9 − 5 × 6 = 24。此外如果运算涉及到一些奇数的运算也会增加难度,比如6、9、9、10的解法为9 × 10 ÷ 6 + 9 = 24。一些例子如下:
数字组合 |
解法 |
数字组合 |
解法 |
1, 3, 9, 10 |
(1 + 10) × 3 − 9 |
7, 8, 8, 10 |
10 × 8 − 7 × 8 |
9, 11, 12, 12 |
11 × 12 − 9 × 12 |
1, 2, 7, 7 |
(7 × 7 − 1) ÷ 2 |
3, 8, 8, 10 |
(8 × 10 − 8) ÷ 3 |
4, 8, 8, 11 |
(8 × 11 + 8) ÷ 4 |
5, 10, 10, 13 |
(10 × 13 − 10) ÷ 5 |
1, 5, 11, 11 |
(11 × 11 - 1) ÷ 5 |
1, 6, 11, 13 |
(11 × 13 + 1) ÷ 6 |
1, 7, 13, 13 |
(13 × 13 − 1) ÷ 7 |
24点的组合数学
其实独立的24点的个数并不多。如果每张牌面的数值被限制在1到K之间,独立的数字组合数由有重复的组合数给出。:
。
譬如,如果最大的牌面数值为10,那么独立的数字组合为715个,远比10000要小;如果最大的牌面数值为13,那么独立的数字组合为1820个。这是因为其他的组合可以通过简单的数字交换得到。
可以用枚举证明,如果最大牌面数值为10,在715个组合中,有149个组合是没有解的。此外,如果我们随机的取4个1-10之间的数字,无解的概率为1442/10000大致为1/7。如果最大牌面数值为13,则会有458个组合无解(总数为1820)。
推广
24点游戏可以被推广到多张牌(n>4)的情形。通常我们假定这n张牌是从一副牌中选出的,也就是说,每个数字至多出现4次。比如在5张牌、 最大数值为10的情况下,有1992种不同的组合方式。其中无解的比例大大降低,一共为如下37种。如果最大数值为13,则无解的总数扩大为80。
1 1 1 1 2 |
1 1 1 1 3 |
1 1 1 1 4 |
1 1 1 1 5 |
1 1 1 2 2 |
1 1 1 2 3 |
1 1 1 9 9 |
1 1 1 9 10 |
1 1 1 10 10 |
1 1 2 2 2 |
1 1 6 7 7 |
1 1 7 7 7 |
1 1 9 9 9 |
1 1 9 9 10 |
1 1 10 10 10 |
1 5 9 9 9 |
1 6 7 7 7 |
1 7 7 7 7 |
1 7 9 9 9 |
1 8 9 9 10 |
1 8 9 10 10 |
1 9 9 9 9 |
1 9 9 9 10 |
1 9 9 10 10 |
1 9 10 10 10 |
1 10 10 10 10 |
2 9 9 9 9 |
3 5 5 5 5 |
4 9 9 9 9 |
6 7 7 7 7 |
6 10 10 10 10 |
8 9 9 9 10 |
8 9 9 10 10 |
9 9 9 9 10 |
9 9 9 10 10 |
9 9 10 10 10 |
9 10 10 10 10 |
|
|
|
|
|
注意到,以上罗列的情形中并没有5个相同数字的组合,比如,5个1,这是因为一副牌最多只有4个相同数字的牌。
在6张牌的情况下,在4905种不同的组合方式仅有3种组合是无解的:1 1 1 1 2 2,9 9 9 10 10 10 和9 9 10 10 10 10。 在7张牌的情况下,所有组合方式(10890种)都有解。