題目
分析
如果不出順子,那麼怎麼出最優是可以DP解決的:表示一副牌有個炸彈、個三張、個順子、個單牌的最優出法。在這個狀態定義下,原來的張牌變成了張牌,你只需要考慮怎麼組合(下面用、、、分別代指“炸彈”“三張”“對子”“單牌”這四種牌):
- 一張一張出完;
- 出一張,狀態轉移;
- 將一張變成一張和一張,狀態轉移(“炸彈”拆成一個三張和一個單牌);
- ……(共種情況,詳見代碼)
這樣就可以輕(jue)松(wang)地完成DP了。
然後Dfs
搜索順子的打法,打了順子過後就直接用上面的值,將剩下的牌打完即可,注意處理一下大小王的情況(這道題裏面大小王不算對子)。
代碼
突然愛上了打空格XD
#include <algorithm>
#include <cstdio>
#include <cstring>
int Read() {
int x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = x * 10 + (c ^ 48), c = getchar();
return x;
}
const int MAXN = 13;
const int MinNum[5] = {0, 5, 3, 2};
int Ans;
int Card[MAXN + 5];
int Fixed[MAXN / 4 + 5][MAXN / 3 +5][MAXN / 2 + 5][MAXN + 5];
// Fixed[四張][三張][對子][單牌]
inline void toMin(int &cur, int upd) {
cur = std::min(cur, upd);
}
void Prepare(int N) {
Fixed[0][0][0][0] = 0;
for (int i = 0; i <= N; i++)
for (int j = 0; j <= N; j++)
for (int k = 0; k <= N; k++) {
for (int l = 0; l <= N; l++) {
if (i * 4 + j * 3 + k * 2 + l > N)
continue;
int &cur = Fixed[i][j][k][l];
cur = i + j + k + l; // 一張一張出完
if (i) {
toMin(cur, Fixed[i - 1][j][k][l] + 1); // 直接出
toMin(cur, Fixed[i - 1][j + 1][k][l + 1]); // 變成三張一樣的和一張單牌出
if (i >= 2) toMin(cur, Fixed[i - 2][j][k][l] + 1); // 兩炸拆成四帶兩個一樣的對子出
if (k >= 1) toMin(cur, Fixed[i - 1][j][k - 1][l] + 1); // 帶上以前的兩個一樣的單牌出
if (k >= 2) toMin(cur, Fixed[i - 1][j][k - 2][l] + 1); // 拆成兩個對子出
if (l >= 2) toMin(cur, Fixed[i - 1][j][k][l - 2] + 1); // 帶上以前的兩個單牌出
}
if (j) {
toMin(cur, Fixed[i][j - 1][k][l] + 1); // 直接出
toMin(cur, Fixed[i][j - 1][k + 1][l + 1]); // 變成一個對子和一張單牌出
if (k >= 1) toMin(cur, Fixed[i][j - 1][k - 1][l] + 1); // 帶上以前的一個對子出
if (l >= 1) toMin(cur, Fixed[i][j - 1][k][l - 1] + 1); // 帶上以前的一張單牌出
}
if (k)
toMin(cur, Fixed[i][j][k - 1][l] + 1); // 直接出
if (l)
toMin(cur, Fixed[i][j][k][l - 1] + 1); // 直接出
}
}
}
int Cnt[MAXN + 5];
inline int Go(int i, int j, int k, int l, int jok){
if (jok <= 1)
return Fixed[i][j][k][l + jok];
return std::min(Fixed[i][j][k][l] + 1, Fixed[i][j][k][l + 2]);
}
void Dfs(int tot) {
if (tot >= Ans)
return;
memset(Cnt, 0, sizeof Cnt);
for (int i = 2; i <= 14; i++)
Cnt[Card[i]]++;
toMin(Ans, tot + Go(Cnt[4], Cnt[3], Cnt[2], Cnt[1], Card[0]));
for (int k = 1; k <= 3; k++)
for (int i = 3; i <= 14; i++) {
int j = i;
while (j <= 14 && Card[j] >= k) {
Card[j] -= k;
if (j - i + 1 >= MinNum[k])
Dfs(tot + 1);
j++;
}
while (--j >= i)
Card[j] += k;
}
}
int main() {
int T = Read(), N = Read();
Prepare(N);
while (T--) {
memset(Card, 0, sizeof Card);
for (int i = 1; i <= N; i++) {
int num = Read(), col = Read();
if (num == 1)
num = 14;
Card[num]++;
}
Ans = N, Dfs(0);
printf("%d\n", Ans);
}
return 0;
}