P2540 [NOIP2015 提高组] 斗地主 加强版

· · 个人记录

思路

优雅,太优雅了!

首先声明,这个思路源自某位大佬。

考虑把所有牌分成两类,不定长的连牌(如顺子、连对、飞机)和其他定长的(如单排、对子、三带一、炸弹等等)。

第一种可以通过暴搜枚举,后一种用 dp 神操作转移。

那么根据单张、对子、三张、炸弹、三带一、三带二、四带二的出牌规则就可以写出 $dp$ 转移了。 但有一种特殊情况,就是三张可以拆分为单排和对子,炸弹可以拆分为单排和三张,那么就有:

\begin{array}{l} f[i + 1][j + 1][k - 1][z][l] \to f[i][j][k][z]][l]\ f[i + 1][j][k + 1][z - 1][l] \to f[i][j][k][z]][l] \end{array}

为什么炸弹不拆解为两个对子、三张不拆解为三个单张呢? 我们拆牌的意义是什么?就是可以凑齐比如三带一、四带二里面的边角余料,出此之外出整牌一定是不劣的。 预处理出这些后,我们 $dfs$ 时暴力枚举所有顺子、连对和飞机,然后加上已经预处理出的剩下的牌的最小出牌次数取 $\min$ 就是答案了。 ## 代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N = 25; int T, n, a[N]; int f[N][N][N][N][3]; int ans; int cnt[10]; void dfs(int x) { int tot; bool flag; if(x >= ans) return; for(int k = 1; k <= 3; k++) for(int i = 1; i <= 12; i++) { flag = 1; if(k == 1) tot = 5; if(k == 2) tot = 3; if(k == 3) tot = 2; while(flag && i + tot - 1 <= 12) { for(int j = 1; j <= tot; j++) if(a[i + j - 1] < k) { flag = 0; break; } if(flag == 0) continue; for(int j = 1; j <= tot; j++) a[i + j - 1] -= k; dfs(x + 1); for(int j = 1; j <= tot; j++) a[i + j - 1] += k; tot++; } } for(int i = 1; i <= 5; i++) cnt[i] = 0; for(int i = 1; i <= 13; i++) cnt[a[i]]++; cnt[5] = a[14]; ans = min(ans, x + f[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]]); } void init() { int x = 100; memset(f, 0x3f, sizeof(f)); f[0][0][0][0][0] = 0; for(int z = 0; z <= n; z++) for(int k = 0; k <= n; k++) for(int i = 0; i <= n; i++) for(int j = 0; j <= n; j++) for(int l = 0; l <= n; l++) { x = 100; if(i > 0) x = min(x, f[i - 1][j][k][z][l] + 1); if(j > 0) x = min(x, f[i][j - 1][k][z][l] + 1); if(k > 0) x = min(x, f[i][j][k - 1][z][l] + 1); if(z > 0) x = min(x, f[i][j][k][z - 1][l] + 1); if(l > 0) x = min(x, f[i][j][k][z][l - 1] + 1); if(l > 1) x = min(x, f[i][j][k][z][l - 2] + 1); //单张、对子、三张、炸弹、单张王、王炸 if(i > 0 && k > 0) x = min(x, f[i - 1][j][k - 1][z][l] + 1); if(k > 0 && l > 0) x = min(x, f[i][j][k - 1][z][l - 1] + 1); //三带一 if(j > 0 && k > 0) x = min(x, f[i][j - 1][k - 1][z][l] + 1); //三带二 if(i > 1 && z > 0) x = min(x, f[i - 2][j][k][z - 1][l] + 1); if(i > 0 && z > 0 && l > 0) x = min(x, f[i - 1][j][k][z - 1][l - 1] + 1); if(z > 0 && l > 1) x = min(x, f[i][j][k][z - 1][l - 2] + 1); if(j > 0 && z > 0) x = min(x, f[i][j - 1][k][z - 1][l] + 1); if(j > 1 && z > 0) x = min(x, f[i][j - 2][k][z - 1][l] + 1); if(z > 1) x = min(x, f[i][j][k][z - 2][l] + 1); //四带二 if(z > 0) x = min(x, f[i + 1][j][k + 1][z - 1][l]); if(k > 0) x = min(x, f[i + 1][j + 1][k - 1][z][l]); //拆单 f[i][j][k][z][l] = min(f[i][j][k][z][l], x); } } void work() { memset(a, 0, sizeof(a)); ans = n; for(int i = 1; i <= n; i++) { int num, col; scanf("%d%d", &num, &col); if(num == 0) { a[14]++; continue; } if(num >= 3) a[num - 2]++; if(num == 1) a[12]++; if(num == 2) a[13]++; } dfs(0); printf("%d\n", ans); } int main() { scanf("%d%d", &T, &n); init(); while(T--) work(); } ```