洛谷P2540 斗地主增强版

23 August 2019 | duanyll | Tags: OI 题解 搜索

一道经典的搜索?题, 早就想做了.

听说考试原题的数据很水, 瞎贪心都能过, 就不说了, 这里分析一下几个直接贪心不能解决的地方.

  • 两个鬼是否拆开(3333鬼鬼)
  • 顺子要截取多少(日常斗地主经验太常见这种情况了)
  • 炸弹和三联拆不拆开(33334444, 理解成四带两对可以一次走掉)
  • 多次顺子/拆炸弹

按照以上几个痛点依次dfs搜索就是了, 具体见代码

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <map>
using namespace std;

typedef long long int64;

const int INF = 0x3f3f3f3f;
const int MAXN = 25;

typedef int card;

const card three = 1;
const card four = 2;
const card five = 3;
const card six = 4;
const card seven = 5;
const card eight = 6;
const card nine = 7;
const card ten = 8;
const card J = 9;
const card Q = 10;
const card K = 11;
const card A = 12;
const card two = 13;
const card grey_joker = 14;
const card red_joker = 15;

card parse(int a, int b) {
    switch (a) {
        case 0:
            if (b == 1)
                return grey_joker;
            else
                return red_joker;
        case 1:
            return A;
        case 2:
            return two;
        case 3:
            return three;
        case 4:
            return four;
        case 5:
            return five;
        case 6:
            return six;
        case 7:
            return seven;
        case 8:
            return eight;
        case 9:
            return nine;
        case 10:
            return ten;
        case 11:
            return J;
        case 12:
            return Q;
        case 13:
            return K;
        default:
            clog << "fuck unknown card" << endl;
            return 0;
    }
}

int n;
int card_count[20];
int style_count[5];

// 考虑最后的情况(贪心匹配)
int get_other() {
    int tmp[5];
    memcpy(tmp, style_count, sizeof tmp);
    int ans = 0;
    while (tmp[4] > 0) {
        tmp[4]--;
        ans++;
        if (tmp[1] >= 2) {  // 带两张单牌
            tmp[1] -= 2;
        } else if (tmp[2] >= 2) {  // 带两对
            tmp[2] -= 2;
        } else if (tmp[2] >= 1) {  // 带一对
            tmp[2]--;
        }
    }
    while (tmp[3] > 0) {
        tmp[3]--;
        ans++;
        if (tmp[1] >= 1) {  // 带一张单牌
            tmp[1]--;
        } else if (tmp[2] >= 1) { // 带一对
            tmp[2]--;
        }
    }
    ans += tmp[2] + tmp[1];
    return ans;
}

// 考虑拆开对子和三连, 炸弹
int dfs_break() {
    int ans = INF;

    // 不拆
    ans = min(ans, get_other());

    // 拆对子 (没意义)
    // if (style_count[2] > 0) {
    //     style_count[2]--;
    //     style_count[1] += 2;
    //     ans = min(ans, get_other());
    //     style_count[1] -= 2;
    //     style_count[2]++;
    // }

    // 拆三联
    if (style_count[3] > 0) {
        style_count[3]--;
        style_count[2]++;
        style_count[1]++;
        ans = min(ans, dfs_break());
        style_count[1]--;
        style_count[2]--;
        style_count[3]++;
    }

    // 拆炸弹
    if (style_count[4] > 0) {
        style_count[4]--;

        style_count[2] += 2;
        ans = min(ans, dfs_break());
        style_count[2] -= 2;

        style_count[1]++;
        style_count[3]++;
        ans = min(ans, dfs_break());
        style_count[3]--;
        style_count[1]--;

        style_count[4]++;
    }

    return ans;
}

// 考虑三种顺子
int dfs_sequence() {
    int ans = INF;

    // 不出顺子
    memset(style_count, 0, sizeof style_count);
    for (card i = three; i <= red_joker; i++) {
        style_count[card_count[i]]++;
    }
    ans = min(ans, dfs_break());

    // 考虑单顺子
    for (card i = three; i + 4 < two; i++) {
        for (card j = i; j < two; j++) {
            if (card_count[j] < 1) break;
            if (j - i < 4) continue;
            for (card k = i; k <= j; k++) {
                card_count[k] -= 1;
            }
            ans = min(ans, dfs_sequence() + 1);
            for (card k = i; k <= j; k++) {
                card_count[k] += 1;
            }
        }
    }

    // 考虑双顺子
    for (card i = three; i + 2 < two; i++) {
        for (card j = i; j < two; j++) {
            if (card_count[j] < 2) break;
            if (j - i < 2) continue;
            for (card k = i; k <= j; k++) {
                card_count[k] -= 2;
            }
            ans = min(ans, dfs_sequence() + 1);
            for (card k = i; k <= j; k++) {
                card_count[k] += 2;
            }
        }
    }

    // 考虑三顺子
    for (card i = three; i + 1 < two; i++) {
        for (card j = i; j < two; j++) {
            if (card_count[j] < 3) break;
            if (j - i < 1) continue;
            for (card k = i; k <= j; k++) {
                card_count[k] -= 3;
            }
            ans = min(ans, dfs_sequence() + 1);
            for (card k = i; k <= j; k++) {
                card_count[k] += 3;
            }
        }
    }

    return ans;
}

// 是否出对鬼
int dfs_joker() {
    int ans = INF;
    if (card_count[grey_joker] > 0 && card_count[red_joker] > 0) {
        card_count[grey_joker]--;
        card_count[red_joker]--;
        ans = min(ans, dfs_sequence() + 1);
        card_count[grey_joker]++;
        card_count[red_joker]++;
    }
    ans = min(ans, dfs_sequence());
    return ans;
}

int main() {
    int tcnt;
    cin >> tcnt >> n;
    for (int T = 1; T <= tcnt; T++) {
        memset(card_count, 0, sizeof card_count);
        for (int i = 1; i <= n; i++) {
            int a, b;
            cin >> a >> b;
            card_count[parse(a, b)]++;
        }
        cout << dfs_joker() << endl;
    }
    // ans = INF;
    return 0;
}

这题要是放cf educational round上会不会全场fst…