zoukankan      html  css  js  c++  java
  • luogu2540 斗地主增强版

    题目大意

      给你一副手牌,没有飞机带翅膀,按斗地主的规则,求将所有牌打出的最少次数。

    题解

    先不考虑顺子

      我们已经知道花色对牌没有影响,那么如果不考虑顺子,每个牌具体是什么数字我们也用不着知道,我们关心的只有牌堆中单张、对子、棒子、炸弹、王的个数。因此我们可以用$f(k_1,k_2,k_3,k_4,k_x)$表示当有$k_1$个单张,$k_2$个对子,$k_3$个棒,$k_4$个炸弹,$k_x$个王时,将牌全部打出的最少次数。而显然这是可以进行DP的。转移方式为:要么不拆牌而出牌,要么拆牌。

    递推的顺序?

      看以下拆牌的递推式:

                            if (k2)//将二拆成两个单张
                                UpdMin(cur, F[k1 + 2][k2 - 1][k3][k4][kx]);
                            if (k3)//将三拆成一个单张和一对
                                UpdMin(cur, F[k1 + 1][k2 + 1][k3 - 1][k4][kx]);
                            if (k4)//将四拆成一个单张和一棒
                                UpdMin(cur, F[k1 + 1][k2][k3 + 1][k4 - 1][kx]);
                            if (k4)//将四拆成两对
                                UpdMin(cur, F[k1][k2 + 2][k3][k4 - 1][kx]);            
    

      我们从外到里考虑。最外层不可以从+1处转移,因此我们把$k_4$选为最外层。此时,从$k_4-1$处的转移就都合法了,我们看从$k_4$转移时,第二层不可以从+1处转移。故第二层选$k_3$。此时,从$k3-1$处的转移就都合法了。当从$k_3$处转移时,第三层不可以从+1处转移……因此,递推顺序为$k_4 ightarrow k_3 ightarrow k_2 ightarrow k_1 ightarrow k_x$。

    考虑顺子呢?

      枚举所有出顺子的方式(暴力搜顺子),然后再在剩余的牌中查DP表即可。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    using namespace std;
    
    #define UpdMin(x, y) x = min(x, y)
    const int MAX_ID = 27, MAX_IDCNT = 7, INF = 0x3f3f3f3f;
    const int IdBegin = 1, IdShunziEnd = 12, IdLast = 13, MaxUnitLen = 3;
    const int UnitCnt[4] = { 0, 5, 3, 2 };
    int F[MAX_ID][MAX_ID][MAX_ID][MAX_ID][MAX_ID];
    int IdCnt[MAX_ID], IdCnt_Cnt[MAX_IDCNT];
    int CardCnt, Ans;
    
    void DoShunzi(int shunziCnt, int idBegin, int unitLen, int unitCnt)
    {
        if (shunziCnt >= Ans)
            return;
        for (int dId = 0; dId < unitCnt; dId++)
            IdCnt[idBegin + dId] -= unitLen;
        memset(IdCnt_Cnt, 0, sizeof(IdCnt_Cnt));
        for (int id = IdBegin; id <= IdLast; id++)
            IdCnt_Cnt[IdCnt[id]]++;
        IdCnt_Cnt[0] = IdCnt[0];
     	UpdMin(Ans, shunziCnt + F[IdCnt_Cnt[1]][IdCnt_Cnt[2]][IdCnt_Cnt[3]][IdCnt_Cnt[4]][IdCnt_Cnt[0]]);
    
        for (int unitLen1 = 1; unitLen1 <= IdLast; unitLen1++)
            for (int idBegin1 = IdBegin; idBegin1 <= IdShunziEnd; idBegin1++)
                for (int unitCnt1 = 1; idBegin1 + unitCnt1 - 1 <= IdShunziEnd && IdCnt[idBegin1 + unitCnt1 - 1] >= unitLen1; unitCnt1++)
                    if (unitCnt1 >= UnitCnt[unitLen1])
                        DoShunzi(shunziCnt + 1, idBegin1, unitLen1, unitCnt1);
    
        for (int dId = 0; dId < unitCnt; dId++)
            IdCnt[idBegin + dId] += unitLen;
    }
    
    void DP()
    {
        memset(F, INF, sizeof(F));
        F[0][0][0][0][0] = 0;
        for (int k4 = 0; k4 <= CardCnt / 4; k4++)
            for (int k3 = 0; k3 <= CardCnt / 3; k3++)
                for (int k2 = 0; k2 <= CardCnt / 2; k2++)
                    for (int k1 = 0; k1 <= CardCnt; k1++)
                        for (int kx = 0; kx <= 2; kx++)
                        {
                            int &cur = F[k1][k2][k3][k4][kx];
                            if (kx >= 2)//火箭
                                UpdMin(cur, F[k1][k2][k3][k4][kx - 2] + 1);
                            if (k4)//炸弹
                                UpdMin(cur, F[k1][k2][k3][k4 - 1][kx] + 1);
                            if (k1)//单张,不是王
                                UpdMin(cur, F[k1 - 1][k2][k3][k4][kx] + 1);
                            if (kx)//单张 ,是王
                                UpdMin(cur, F[k1][k2][k3][k4][kx - 1] + 1);
                            if (k2)//对子
                                UpdMin(cur, F[k1][k2 - 1][k3][k4][kx] + 1);
                            if (k3)//三张
                                UpdMin(cur, F[k1][k2][k3 - 1][k4][kx] + 1);
                            if (k3 && k1)//三带一,一不是王
                                UpdMin(cur, F[k1 - 1][k2][k3 - 1][k4][kx] + 1);
                            if (k3 && kx)//三带一,一是王
                                UpdMin(cur, F[k1][k2][k3 - 1][k4][kx - 1] + 1);
                            if (k3 && k2)//三带二,二都不是王
                                UpdMin(cur, F[k1][k2 - 1][k3 - 1][k4][kx] + 1);
                            if (k4 && k1 >= 2)//四带二单,二都不是王
                                UpdMin(cur, F[k1 - 2][k2][k3][k4 - 1][kx] + 1);
                            if (k4 && k1 && kx)//四带二单,二中一张不是王,一张是王
                                UpdMin(cur, F[k1 - 1][k2][k3][k4 - 1][kx - 1] + 1);
                            if (k4 && k2 >= 2)//四带二对
                                UpdMin(cur, F[k1][k2 - 2][k3][k4 - 1][kx] + 1);
    
                            if (k2)//将二拆成两个单张
                                UpdMin(cur, F[k1 + 2][k2 - 1][k3][k4][kx]);
                            if (k3)//将三拆成一个单张和一对
                                UpdMin(cur, F[k1 + 1][k2 + 1][k3 - 1][k4][kx]);
                            if (k4)//将四拆成一个单张和一棒
                                UpdMin(cur, F[k1 + 1][k2][k3 + 1][k4 - 1][kx]);
                            if (k4)//将四拆成两对
                                UpdMin(cur, F[k1][k2 + 2][k3][k4 - 1][kx]);
                        }
    }
    
    int main()
    {
        int t;
        scanf("%d%d", &t, &CardCnt);
        DP();
        while (t--)
        {
            Ans = INF;
            memset(IdCnt, 0, sizeof(IdCnt));
            for (int i = 1; i <= CardCnt; i++)
            {
                int id, color;
                scanf("%d%d", &id, &color);
                id =
                    id == 1 ? 12 :
                    id == 2 ? 13 :
                    id == 0 ? 0 :
                    id - 2;
                IdCnt[id]++;
            }
            DoShunzi(0, 0, 0, 0);
            printf("%d
    ", Ans);
        }
        return 0;
    }
    

      

  • 相关阅读:
    第二周作业
    第一周作业
    抓老鼠啊~亏了还是赚了?
    打印沙漏
    寒假作业2
    我与老师
    自我介绍
    第九周课程总结&实验报告(七)
    第八周课程总结&实验报告(六)
    第七周实验报告(五)&周总结
  • 原文地址:https://www.cnblogs.com/headboy2002/p/9783954.html
Copyright © 2011-2022 走看看