吳昊品游戲核心算法 Round 3 —— 速算24點AI(HDOJ 1427)


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


  1 #include<stdio.h>
  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  0return a+b;
 22 
 23       case  1return a*b;
 24 
 25       case  2if(a>b)  return a-b;
 26 
 27               else  return b-a;
 28 
 29       case  3if(a>=b&&b!= 0&&a%b== 0return a/b;
 30 
 31               else  if(a<b&&a!=b&&b%a== 0return 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==- 1continue;
 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==- 1continue;
 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==- 1continue;
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==- 1continue;
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種)都有解。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM