[洛谷P2540]【NOIP2015】斗地主增强版(DP+搜索)

文章目录

题目

P2540 斗地主增强版

分析

如果不出顺子,那么怎么出最优是可以DP解决的: d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l]表示一副牌有 i i i个炸弹、 j j j个三张、 k k k个顺子、 l l l个单牌的最优出法。在这个状态定义下,原来的 4 i + 3 j + 2 k + l 4i+3j+2k+l 4i+3j+2k+l张牌变成了 i + j + k + l i+j+k+l i+j+k+l张牌,你只需要考虑怎么组合(下面用 i i i j j j l l l l l l分别代指“炸弹”“三张”“对子”“单牌”这四种牌):

  • 一张一张出完;
  • 出一张 i i i,状态转移;
  • 将一张 i i i变成一张 j j j和一张 l l l,状态转移(“炸弹”拆成一个三张和一个单牌);
  • ……(共 13 13 13种情况,详见代码)

这样就可以轻(jue)松(wang)地完成DP了。

然后Dfs搜索顺子的打法,打了顺子过后就直接用上面的 d p dp dp值,将剩下的牌打完即可,注意处理一下大小王的情况(这道题里面大小王不算对子)。

代码

突然爱上了打空格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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值