• Luogu 2668 NOIP 2015 斗地主(搜索,动态规划)


    Luogu 2668 NOIP 2015 斗地主(搜索,动态规划)

    Description

    牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。

    现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。

    需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。

    具体规则如下:
    此处输入图片的描述

    Input

    第一行包含用空格隔开的2个正整数Tn,表示手牌的组数以及每组手牌的张数。
    接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。 特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。

    Output

    共T行,每行一个整数,表示打光第i手牌的最少次数。

    Sample Input

    1 8
    7 4
    8 4
    9 1
    10 4
    11 1
    5 1
    1 4
    1 1

    Sample Output

    3

    Http

    Luogu:https://www.luogu.org/problem/show?pid=2668

    Source

    搜索,动态规划

    解决思路

    题目中给出的出牌规则比较复杂,我们简单地分一下类

    第一类:牌的数字与出牌方式没有关系:单张,对子,三张,双王,三带一,三带二,四带二,炸弹
    第二类:单顺子,双顺子,三顺子

    我们发现,如果只按照第一类的方式打出,当每一种牌(有一张的,有两张的,有三张的,有四张的)的数量一定时,最少的出牌步数是一定的。所以我们考虑把这个先预处理出来,然后搜索顺子的情况。
    (F[i][j][k][l])表示单张(i)张,对子(j)对,三张的(k)组,炸弹(l)组时的最少步数。我们有下面的转移方程:(注意,都要在有意义的情况下转移,比如说(i,j,k,l)不能出现负值

    考虑单张
    F[i][j][k][l]=min(F[i][j][k][l],F[i-1][j][k][l]+1);//打出一个单牌
    考虑对子
    F[i][j][k][l]=min(F[i][j][k][l],F[i][j-1][k][l]+1);//打出一个对子
    考虑三张
    F[i][j][k][l]=min(F[i][j][k][l],F[i][j][k-1][l]+1);//打出三张牌
    F[i][j][k][l]=min(F[i][j][k][l],F[i-1][j][k-1][l]+1);//打出三带一
    F[i][j][k][l]=min(F[i][j][k][l],F[i][j-1][k-1][l]+1);//打出三带二
    考虑四张
    F[i][j][k][l]=min(F[i][j][k][l],F[i][j][k][l-1]+1);//打出四张牌
     F[i][j][k][l]=min(F[i][j][k][l],F[i-2][j][k][l-1]+1);//打出四带两张单牌
     F[i][j][k][l]=min(F[i][j][k][l],F[i][j-2][k][l-1]+1);//打出四带两对牌
    

    然后我们搜索顺子的情况。可以作为顺子的是3~A,所以为了方便,我们在输入的时候把1变成14。注意,顺子可以不打完,双顺子可以拆成单顺子,三顺子可以拆成单顺子或双顺子。最后统计最优解。
    另外需要注意的是,两张王既可以看作两张单牌,也可以看作一对子。
    本题加强版:http://www.cnblogs.com/SYCstudio/p/7628971.html

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxN=25;
    const int Shunzi[4]={0,5,3,2};//i顺子至少需要多少张牌
    const int inf=2147483647;
    
    int n;
    int Card[maxN];//记录每一种牌剩余的数量
    int F[maxN][maxN][maxN][maxN];
    int Ans;
    
    void init();
    void dfs(int step);
    int now_step(int x1,int x2,int x3,int x4);//计算当前如果只按照第一类方式打出牌的最少步数,x1~x4分别是1张~4张牌的数量
    
    int main()
    {
        int T;
        scanf("%d%d",&T,&n);
        init();
        while (T--)//多组数据
        {
            memset(Card,0,sizeof(Card));
            Ans=n;
            for (int i=1;i<=n;i++)//输入
            {
                int a,b;
                scanf("%d%d",&a,&b);
                if (a==0)//0是王
                    Card[0]++;
                else
                    if (a==1)//将'A'单独拿出来,变成14,方便处理顺子
                        Card[14]++;
                    else
                        Card[a]++;
            }
            dfs(0);
            printf("%d
    ",Ans);
        }
        return 0;
    }
    
    void init()//计算出F
    {
        F[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++)
                    {
                        F[i][j][k][l]=i+j+k+l;
                        if (i+2*j+3*k+4*l<=n)
                        {
                            if (i!=0)//一张牌
                                F[i][j][k][l]=min(F[i][j][k][l],F[i-1][j][k][l]+1);//打出一个单牌
                            if (j!=0)//双牌
                                F[i][j][k][l]=min(F[i][j][k][l],F[i][j-1][k][l]+1);//打出一个对子
                            if (k!=0)//三张牌
                            {
                                F[i][j][k][l]=min(F[i][j][k][l],F[i][j][k-1][l]+1);//打出三张牌
                                if (i!=0)
                                    F[i][j][k][l]=min(F[i][j][k][l],F[i-1][j][k-1][l]+1);//打出三带一
                                if (j!=0)
                                    F[i][j][k][l]=min(F[i][j][k][l],F[i][j-1][k-1][l]+1);//打出三带二
                            }
                            if (l!=0)//四张牌
                            {
                                F[i][j][k][l]=min(F[i][j][k][l],F[i][j][k][l-1]+1);//打出四张牌
                                if (i>=2)
                                    F[i][j][k][l]=min(F[i][j][k][l],F[i-2][j][k][l-1]+1);//打出四带两张单牌
                                if (j>=2)
                                    F[i][j][k][l]=min(F[i][j][k][l],F[i][j-2][k][l-1]+1);//打出四带两对牌
                            }
                            //printf("(%d,%d,%d,%d) %d
    ",i,j,k,l,F[i][j][k][l]);
                        }
                    }
        return;
    }
    
    void dfs(int step)//搜索顺子,step表示当前打出多少个顺子
    {
        if (step>Ans)//最优性剪枝
            return;
        int Cnt[maxN];
        memset(Cnt,0,sizeof(Cnt));//记录有1张~4张的牌分别有多少种
        for (int i=2;i<=14;i++)//注意这里要统计2
            Cnt[Card[i]]++;
        Ans=min(Ans,step+now_step(Cnt[1],Cnt[2],Cnt[3],Cnt[4]));//计算当前剩余的都按照第一类方式打出的最少步数
        for (int k=1;k<=3;k++)//枚举顺子or双顺子or三顺子
        {
            for (int i=3;i<=14;i++)//注意这里从3开始
            {
                int pos;
                for (pos=i;(pos<=14)&&(Card[pos]>=k);pos++)
                {
                    Card[pos]=Card[pos]-k;//减去顺子
                    if (pos-i+1>=Shunzi[k])
                        dfs(step+1);//当满足构成一个顺子时,就可以先打出去
                }
                for (pos=pos-1;pos>=i;pos--)//还原
                    Card[pos]=Card[pos]+k;
            }
        }
        return;
    }
    
    int now_step(int x1,int x2,int x3,int x4)
    {
        if (Card[0]==0)
            return F[x1][x2][x3][x4];//没有王
        if (Card[0]==1)
            return F[x1+1][x2][x3][x4];//只有一个王
        if (Card[0]==2)
            return min(F[x1+2][x2][x3][x4],F[x1][x2][x3][x4]+1);//将两张王看作两张单牌or一对牌
    }
    
  • 相关阅读:
    C++如何调用父类中的方法
    关于QStandardItemMode的资料
    Qt的信号和槽的使用方法练习
    Redirecting Standard I/O to Windows Console
    C++头文件的工作原理
    《深入浅出MFC》第七章 简单而完整:MFC骨干程序
    《深入浅出MFC》第六章 MFC程序的生死因果
    《深入浅出MFC》第五章 总观Application Framework
    《深入浅出MFC》第四章 Visual C++集成开发环境
    nexus使用记录
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7625882.html
Copyright © 2020-2023  润新知