# UVA 11210 - Chinese Mahjong
題目連結:
[Lucky Cat](http://luckycat.kshs.kh.edu.tw/homework/q11210.htm)
[Uva](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2151)
這題敘述有點繁雜...也很難簡單說明
還是麻煩點[Lucky Cat](http://luckycat.kshs.kh.edu.tw/homework/q11210.htm)進去看看吧
麻將還真是有夠麻煩的...
研究題目約10~20分鐘
觀察了一下,其實測資憑感覺不大,所以就想要BFS暴力解
就直接實作了
## 大概做法:
1. 將34個牌(1S、4T、NAN...)
換成電腦容易儲存判斷的數字編號
在此是將他們視作0~33
2. 依據題目需求,我們可以得知比較需要的資訊是每個牌的數量,因此記錄下來
3. 開始依據儲存的資料BFS
依據題意,如果是聽牌的狀態,那麼一定可以拆成好幾組面子的和一組不完整缺一個的面子
因此,只要BFS拆現有的面子,拆到最後剩下不能再拆,判斷是否可以組成另一個面子即可
## 詳細作法:
```cpp=
#define card_size 34//共34種牌
```
首先這題一個蠻常用的值我先define了
避免打一堆34哪裡打錯或是怎樣害我找不出bug
這代表牌組的總數目
```cpp=
struct Card
{
int num[card_size],all_num=13;
};
```
儲存一個牌組的結構,分別記錄這個牌組的每個牌有多少數量,總共有幾個
```cpp=
queue<Card> toBFS;
toBFS.push(card);
```
toBFS是要暴力BFS用的佇列
把儲存牌組數量的結構一一丟進去
讓他一一往下搜尋
```cpp=
Card x=toBFS.front();
toBFS.pop();
```
進入到BFS裡面,用x來判斷下一個要丟進toBFS的、或是是否達到聽牌
toBFS就pop掉了
```cpp=
for(int i=0;i<card_size;i++)
{
if(x.num[i]>=3)
{
x.num[i]-=3;
x.all_num-=3;
toBFS.push(x);
x.num[i]+=3;
x.all_num+=3;
}
if(i<27&&i%9>1&&x.num[i]&&x.num[i-1]&&x.num[i-2])//chow
{
for(int j=i-2;j<=i;j++)x.num[j]--;
x.all_num-=3;
toBFS.push(x);
for(int j=i-2;j<=i;j++)x.num[j]++;
x.all_num+=3;
}
}
```
在BFS裡面開始用儲存牌組資料的x往下搜,
只要滿足題目敘述的就可以往下搜尋
```cpp=
if(x.all_num==1)
for(int i=0;i<card_size;i++)
if(x.num[i])
ans[i]=true;
if(x.all_num==2)
{
for(int i=0;i<card_size;i++)
{
if(x.num[i]==2)
{
ans[i]=true;
break;
}
else if(i<27&&i%9>1&&x.num[i]+x.num[i-1]+x.num[i-2]==2)
ans[x.num[i]?x.num[i-1]?i-2:i-1:i]=true;
}
}
```
往下搜到最底的時候,判斷x牌組,找出是否有沒有聽某張牌
基本上這樣整體BFS就大概完成了
```cpp=
if(ans[i]&&card.num[i]!=4)
{
cout<<' '<<card_name[i];
isnt_ready=0;
}
```
如果原本某張牌的數量為4,就算BFS結果為對,他也無法聽牌
因為最多只有4張牌,都在他手上了
## 完整程式碼
```cpp=
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define card_size 34//共34種牌
//紀錄牌類名字,方便輸出
const string card_name[card_size]=
{"1T","2T","3T","4T","5T","6T","7T","8T","9T",
"1S","2S","3S","4S","5S","6S","7S","8S","9S",
"1W","2W","3W","4W","5W","6W","7W","8W","9W",
"DONG","NAN","XI","BEI","ZHONG","FA","BAI"};
//紀錄正在聽甚麼牌、以及是否在聽牌
bool ans[card_size],isnt_ready=1;
//一個紀錄每個card數量及總數的struct
struct Card
{
int num[card_size],all_num=13;
}card;
inline void Init()//初始化 (用於每次結束,把卡片數量歸零避免判斷到上次的)
{
memset(card.num,0,sizeof(card.num));
memset(ans,0,sizeof(ans));
card.all_num=13;
isnt_ready=1;
return;
}
//把字串轉成卡片ID,再記錄該ID的數量 (用於一開始輸入,轉換成可用資料)
inline void string_to_card_num(string k)
{
if(k[0]>='1'&&k[0]<='9')
card.num[k[0]-'1'+(k[1]=='T'?0:(k[1]=='S'?9:18))]++;
else
for(int i=27;i<card_size;i++)
if(k==card_name[i])card.num[i]++;
return ;
}
int main()
{
cin.tie(0);
ios_base::sync_with_stdio(0);
int Case=1;//第幾筆測資
string input_card[13];//牌組
while(cin>>input_card[0])
{
if(input_card[0]=="0")break;
//紀錄牌組內容
string_to_card_num(input_card[0]);
for(int i=1;i<13;i++)
{
cin>>input_card[i];
string_to_card_num(input_card[i]);
}
//BFS用佇列,把card和已吃過eye的丟進去
queue<Card> toBFS;
toBFS.push(card);
//題目需求應該不能超出一次eye,所以要在BFS外丟eye
for(int i=0;i<card_size;i++)
{
if(card.num[i]>=2)
{
card.num[i]-=2;
card.all_num-=2;
toBFS.push(card);
card.num[i]+=2;
card.all_num+=2;
}
}
//開始BFS
//這裡有個問題,沒有紀錄已走過的地方,所以會搜尋較多次,時間較長,待改善
while(!toBFS.empty())
{
Card x=toBFS.front();
toBFS.pop();
//剩1張,可以用eye作為聽牌
if(x.all_num==1)
for(int i=0;i<card_size;i++)
if(x.num[i])
ans[i]=true;
//剩2張,可能可以用pong或chow或都不是作為聽牌
if(x.all_num==2)
{
for(int i=0;i<card_size;i++)
{
if(x.num[i]==2)//當有某個種類有2張時,可以達成pong
{
ans[i]=true;
break;
}
//如果可以達成特定3個連續,可以達成chow
else if(i<27&&i%9>1&&x.num[i]+x.num[i-1]+x.num[i-2]==2)
ans[x.num[i]?x.num[i-1]?i-2:i-1:i]=true;
}
}
for(int i=0;i<card_size;i++)
{
if(x.num[i]>=3)//pong
{
x.num[i]-=3;//把pong的丟進queue
x.all_num-=3;//總數一樣扣掉
toBFS.push(x);
x.num[i]+=3;//加回來,繼續之後的判斷
x.all_num+=3;
}
if(i<27&&i%9>1&&x.num[i]&&x.num[i-1]&&x.num[i-2])//chow
{
for(int j=i-2;j<=i;j++)x.num[j]--;
x.all_num-=3;
toBFS.push(x);
for(int j=i-2;j<=i;j++)x.num[j]++;
x.all_num+=3;
}
}
//*測試用*/for(int i=0;i<card_size;i++)cout<<x.num[i]<<' ';cout<<endl;
}
//輸出部分
cout<<"Case "<<Case++<<':';
for(int i=0;i<card_size;i++)
if(ans[i]&&card.num[i]!=4)
{
cout<<' '<<card_name[i];
isnt_ready=0;
}
if(isnt_ready)cout<<" Not ready";
cout<<'\n';
Init();//初始化
}
return 0;
}
```
## 心得
這題我猜應該正解是DP吧
BFS畢竟是暴力解的方法...
雖然我丟上去是0.000s就是了
其中認為還有些可以改良的地方
像是BFS過程中重複判斷的太多了
因為沒有像一些BFS用visited之類的方法紀錄避免重複判斷,資料太多了
雖然不會無窮迴圈,因為牌組總數量越來越小
但總之可以在優化...只是一時還想不到簡單好寫的方法...
大概就這樣
接著可以慢慢思考其他解法吧
如果測資更大,變成100個、1000個牌或更多
可能真的就DP解吧
或是這題真的本來就暴力解...
DP是純粹我想太多