• 给定一副扑克牌,牌面随机,按照斗地主的方式打出,求最少出完要多少次
  • 规则:
    • 单顺子五个起,最多到A,不包括2和王
    • 连对从三对起,最多到A,不包括2和王
    • 飞机两个起,最多到A,不包括2和王,不可带牌
    • 三张可以带一张也可以带一对,可以单打
    • 四张可以带两张或者两对,可以单打
    • 大小王可以当火箭出,但是打带牌的时候不能当对牌带
    • 一对和单牌和斗地主完全一样

思路

DFS

  • 大暴力搜索
  • 考虑暴力枚举每一种出发,进行爆搜

状态压缩优化

最暴力的爆搜一定会T掉,考虑记忆化搜索:对于某种状态下的牌,如果已经搜过,就不再搜。

所有的状态可以用一个五进制数存下来(因为每种点数的牌最多有四张),然后转化成十进制数,恰好可以用C++中的unsigned long long存下。

接下来我们用一个哈希表(map也是滋磁的),来存储每一种状态,每次搜索前查表,如果已经搜过,则直接返回值,没搜过,在本次搜索结束返回值时记录本种状态下的答案。

代码

我用了一个数组来存每种牌有几张,其中0,1表示大小王,2~10代表2~10的对应点数,11~13表示J~K,14表示A

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <tr1/unordered_map>
#include <climits>
#define MAXN 15
#define update() ans = min(ans, dfs() + 1)
using namespace std;
typedef unsigned long long Status;
int Hand[MAXN];
inline Status zip () {
Status s = 0;
for (int i = 0; i < MAXN; i++) {
s = s * 5 + Hand[i];
}
return s;
}
/*inline void print(){
for(int i = 0; i < MAXN; i++){
printf("%d ", Hand[i]);
}
putchar('\n');
}*/
tr1::unordered_map<Status, int> S;
int dfs() {
//print();
Status s = zip();
if (s == 0) return 0;
if (S.count(s)) return S[s];
int ans = INT_MAX;
bool SSingle = true;
//Joker
if (Hand[0] && Hand[1]) {
Hand[0] = Hand[1] = 0;
update();
Hand[0] = Hand[1] = 1;
SSingle = false;
}//某玄学优化,有对王先出对王,好像这样会更快
//34567
for (int i = 3; i < MAXN - 4; i++) {
bool flag = false;
for (int d = 0; d < 5; d++) {
if (!Hand[i + d]) {
flag = true;
break;
}
}
if (flag) continue;
for (int d = 0; d < 5; d++) Hand[i + d]--;
update();
int j;
for (j = i + 5; j < MAXN && Hand[j]; j++) {
Hand[j]--;
update();
}
for (int k = i; k < j; k++) Hand[k]++;
SSingle = false;
}
//334455
for (int i = 3; i < MAXN - 2; i++) {
bool flag = false;
for (int d = 0; d < 3; d++)
if (Hand[i + d] < 2) {
flag = true;
break;
}
if (flag) continue;
for (int d = 0; d < 3; d++) Hand[i + d] -= 2;
update();
int j;
for (j = i + 3; j < MAXN && Hand[j] >= 2; j++) {
Hand[j] -= 2;
update();
}
for (int k = i; k < j; k++) Hand[k] += 2;
SSingle = false;
}
//333444
for (int i = 3; i < MAXN - 1; i++) {
bool flag = false;
for (int d = 0; d < 2; d++)
if (Hand[i + d] < 3) {
flag = true;
break;
}
if (flag) continue;
for (int d = 0; d < 2; d++) Hand[i + d] -= 3;
update();
int j;
for (j = i + 2; j < MAXN && Hand[j] >= 3; j++) {
Hand[j] -= 3;
update();
}
for (int k = i; k < j; k++) Hand[k] += 3;
SSingle = false;
}
//3 + x
for (int i = 2; i < MAXN; i++)
if (Hand[i] >= 3) {
Hand[i] -= 3;
update();
for (int j = 0; j < MAXN; j++)
if (Hand[j] >= 1) {
Hand[j]--;
update();
Hand[j]++;
}
for (int j = 2; j < MAXN; j++)
if (Hand[j] >= 2) {
Hand[j] -= 2;
update();
Hand[j] += 2;
}
Hand[i] += 3;
SSingle = false;
}
//4 + x
for (int i = 2; i < MAXN; i++)
if (Hand[i] == 4) {
Hand[i] -= 4;
update();
for (int j = 0; j < MAXN; j++) if (Hand[j] >= 1){
Hand[j]--;
for (int k = 0; k < MAXN; k++) if (Hand[k] >= 1) {
Hand[k]--;
update();
Hand[k]++;
}
Hand[j]++;
}
for (int j = 2; j < MAXN; j++) if (Hand[j] >= 2) {
Hand[j] -= 2;
for (int k = 2; k < MAXN; k++) if (Hand[k] >= 2) {
Hand[k] -= 2;
update();
Hand[k] += 2;
}
Hand[j] += 2;
}
Hand[i] += 4;
SSingle = false;
}
//2
for (int i = 2; i < MAXN; i++) if (Hand[i] >= 2) {
Hand[i] -= 2;
update();
Hand[i] += 2;
SSingle = false;
}
//1
if (SSingle) {
ans = 0;
for (int i = 0; i < MAXN; i++) {
ans += Hand[i];
}
}
return S[s] = ans;
}
int main () {
int T, n;
scanf("%d %d", &T, &n);
while (T--) {
memset(Hand, 0, sizeof(Hand));
S.clear();
for (int i = 0; i < n; i++) {
int x, y; scanf("%d %d", &x, &y);
if (x == 0) Hand[y - 1]++;
else if (x == 1) Hand[14]++;
else Hand[x]++;
}
printf("%d\n", dfs());
}
return 0;
}

原来感觉难道爆炸的题,真正做完以后却感觉就是一道大暴力的题,想了很久也没有什么可写的。。最后说一句,tr1害死人。。