Noip2015 Day1 T3 斗地主(Dfs+Dp优化)

题目描述

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

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

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

具体规则如下:

Noip2015 Day1 T3 斗地主(Dfs+Dp优化)_第1张图片

输入输出格式

输入格式:

第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。

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

输出格式:

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

输入输出样例

输入样例#1:

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

输出样例#1:

3

输入样例#2:

1 17
12 3
4 3
2 3
5 4
10 2
3 3
12 2
0 1
1 3
10 1
6 2
12 1
11 3
5 2
12 4
2 2
7 2

输出样例#2:

6

说明

样例1说明

共有1组手牌,包含8张牌:方片7,方片8,黑桃9,方片10,黑桃J,黑桃5,方片A以及黑桃A。可以通过打单顺子(方片7,方片8,黑桃9,方片10,黑桃J),单张牌(黑桃5)以及对子牌(黑桃A以及方片A)在3次内打光。

对于不同的测试点, 我们约定手牌组数T与张数n的规模如下:

Noip2015 Day1 T3 斗地主(Dfs+Dp优化)_第2张图片

数据保证:所有的手牌都是随机生成的。

思路

这道题本来是只拿30分的,然后看了别人的题解才改出来的

30分还是很好写的,对几种情况判断一下就好了

至于想拿满分,首先要明确一点就是,这个题目中,花色无用,大小无用

而且这道题唯一对最后的答案有影响的就是顺子,除此之外最后的结果都是固定的

那我们就可以用一个DP数组来存固定牌组(i个炸弹,j个三张,k个对子,l张单牌)时出完所有牌的步数

然后深搜处理一下顺子的情况

剩下的直接看代码吧

代码

#include
#include
#include
using namespace std;
int dp[25][25][25][25],card[15],h[15];
//dp[i][j][k][l]表示i组四张,j组三张,k组两张,l组单牌所需要的最少步数 
int n,t,color,point,ans;
int tnum[5]={0,5,3,2}; 
inline int read()
{
    int sum=0;
    char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch<='9'&&ch>='0')
    {
        sum=sum*10+ch-48;
        ch=getchar();
    }
    return sum;
}
int dk(int o,int t2,int t3,int f,int k) //处理joker
{
    if(k==1) //只有一张joker的时候 
    {
        o++; //就把joker视为一张单牌 
        k=0; 
    }
    if(k==0) return dp[f][t3][t2][o];
    else return min(dp[f][t3][t2][o+2],dp[f][t3][t2][o]+1);
}
void dfs(int t) //t记录的是 目前为止的次数 
{
    if(t>ans) return;  //边界条件,当当前次数大于最差情况直接return 
    memset(h,0,sizeof(h));
    for(int i=2;i<=14;i++) h[card[i]]++;
    ans=min(ans,t+dk(h[1],h[2],h[3],h[4],card[0])); //先把带有joker的情况变成常规情况 

    for(int k=1;k<=3;k++) //把顺子先处理掉 
     for(int i=3;i<=14;i++)
     {
        int j;
        for(j=i;j<=14&&card[j]>=k;j++)
        {
            card[j]-=k;
            if(j-i+1>=tnum[k]) dfs(t+1);
        }
        for(j--;j>=i;j--) card[j]+=k;
     }
}
void pre()
{
    memset(dp,1,sizeof(dp)); 
    dp[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) //确保是合法的情况 
        {
            dp[i][j][k][l]=i+j+k+l; //这是最差的情况 
            if(i)
            {
                if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);// 四带一对  
                if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);// 一对单牌 
                dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1);           //啥都不带 
            }
            if(j)
            {
                if(k) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);  // 3带一对 
                if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);  // 3带单 
                dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1);          // 什么都不带 
            }
            if(k) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1);        //对子 
            if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1);        //单牌 
        }   
}
int main()
{
    t=read();n=read();
    pre();//预处理
    while(t--)
    {
        memset(card,0,sizeof(card)); //每次记得清零 
        ans=n; //先设置最坏的情况 
        for(int i=1;i<=n;i++)
        {
            point=read(),color=read();
            if(point==1) card[14]++; 
            //因为2不能算顺子,所以将A变为14方便发顺子 
            else card[point]++;
        }
        dfs(0); //遍历 
        printf("%d\n",ans);
    } 
    return 0;
}

诈尸,这道题竟然还有 数据加强版 其实也只是Dp数组追加几个条件而已

自己看代码吧

#include
#include
#include
using namespace std;
int dp[25][25][25][25],card[15],h[15];
//dp[i][j][k][l]表示i组四张,j组三张,k组两张,l组单牌所需要的最少步数 
int n,t,color,point,ans;
int tnum[5]={0,5,3,2}; 
inline int read()
{
    int sum=0;
    char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch<='9'&&ch>='0')
    {
        sum=sum*10+ch-48;
        ch=getchar();
    }
    return sum;
}
int dk(int o,int t2,int t3,int f,int k) //处理joker
{
    if(k==1) //只有一张joker的时候 
    {
        o++; //就把joker视为一张单牌 
        k=0; 
    }
    if(k==0) return dp[f][t3][t2][o];
    else return min(dp[f][t3][t2][o+2],dp[f][t3][t2][o]+1);
}
void dfs(int t) //t记录的是 目前为止的次数 
{
    if(t>ans) return;  //边界条件,当当前次数大于最差情况直接return 
    memset(h,0,sizeof(h));
    for(int i=2;i<=14;i++) h[card[i]]++;
    ans=min(ans,t+dk(h[1],h[2],h[3],h[4],card[0])); //先把带有joker的情况变成常规情况 

    for(int k=1;k<=3;k++) //把顺子先处理掉 
     for(int i=3;i<=14;i++)
     {
         int j;
        for(j=i;j<=14&&card[j]>=k;j++)
        {
            card[j]-=k;
            if(j-i+1>=tnum[k]) dfs(t+1);
        }
        for(j--;j>=i;j--) card[j]+=k;
     }
}
void pre()
{
    dp[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)
    {
        dp[i][j][k][l]=i+k+j+l;//手牌最差 
        if(i)
        {
            if(i>=1) dp[i][j][k][l]=min(dp[i-1][j+1][k][l+1],dp[i][j][k][l]);//四拆成三张和一张单牌
            if(k>=2) dp[i][j][k][l]=min(dp[i-1][j][k-2][l]+1,dp[i][j][k][l]);//四带两对
            if(i>=2) dp[i][j][k][l]=min(dp[i-2][j][k][l]+1,dp[i][j][k][l]);  //两炸拆成四带两对 
            if(k>=1) dp[i][j][k][l]=min(dp[i-1][j][k-1][l]+1,dp[i][j][k][l]);//四带两张成对的单
            if(l>=2) dp[i][j][k][l]=min(dp[i-1][j][k][l-2]+1,dp[i][j][k][l]);//四带二
            dp[i][j][k][l]=min(dp[i-1][j][k][l]+1,dp[i][j][k][l]);
        }
        if(j)
        {
            if(j>=1) dp[i][j][k][l]=min(dp[i][j-1][k+1][l+1],dp[i][j][k][l]); //三拆成一对和一单
            if(k>=1) dp[i][j][k][l]=min(dp[i][j-1][k-1][l]+1,dp[i][j][k][l]); //三带一对
            if(l>=1) dp[i][j][k][l]=min(dp[i][j-1][k][l-1]+1,dp[i][j][k][l]); //三带一
            dp[i][j][k][l]=min(dp[i][j-1][k][l]+1,dp[i][j][k][l]);
        }
        if(k) dp[i][j][k][l]=min(dp[i][j][k-1][l]+1,dp[i][j][k][l]);//对
        if(l) dp[i][j][k][l]=min(dp[i][j][k][l-1]+1,dp[i][j][k][l]);//单
    }
}
int main()
{
    t=read();n=read();
    pre();//预处理
    while(t--)
    {
        memset(card,0,sizeof(card)); //每次记得清零 
        ans=n; //先设置最坏的情况 
        for(int i=1;i<=n;i++)
        {
            point=read(),color=read();
            if(point==1) card[14]++; 
            //因为2不能算顺子,所以将A变为14方便发顺子 
            else card[point]++;
        }
        dfs(0); //遍历 
        printf("%d\n",ans);
    } 
    return 0;
}

你可能感兴趣的:(noip提高组,搜索,动态规划)