如圖所示,這是一款基於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種)都有解。