1. 程式人生 > >luogu2540 鬥地主增強版

luogu2540 鬥地主增強版

個數 影響 space xunit 鬥地主 cpp d+ define \n

題目大意

  給你一副手牌,沒有飛機帶翅膀,按鬥地主的規則,求將所有牌打出的最少次數。

題解

先不考慮順子

  我們已經知道花色對牌沒有影響,那麽如果不考慮順子,每個牌具體是什麽數字我們也用不著知道,我們關心的只有牌堆中單張、對子、棒子、炸彈、王的個數。因此我們可以用$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\rightarrow k_3\rightarrow k_2\rightarrow k_1\rightarrow 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\n", Ans);
    }
    return 0;
}

  

luogu2540 鬥地主增強版