【NOIP2015】斗地主(及加强版&原版)

本文探讨了基于斗地主游戏中牌局的算法问题,包括普通版、加强版及原版的不同规则下的最少出牌次数求解。文章通过搜索与贪心策略结合DP方法来优化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

  给定一副牌,保证合法(即牌包括2,3,4,5,6,7,8,9,10,J,Q,K,A,王,并且每种牌不超过四张,王不超过两张),给定合法的出牌方案,求最少出牌次数。
  方案:
  ①.火箭,即双王
  ②.炸弹,即4张面值相同的牌
  ③.单张和对子
  ④.三不带,即3张面值相同的牌
  ⑤.三带一单或三带一对
  ⑥.单顺子,即面值连续的五张以上的牌
  ⑦.双顺子,基本同上
  ⑧.三顺子,基本同上
  ⑨.四带二单(包括四带一对)或四带二对(包括四带四,例如44445555就可以一次打出)

解法

搜索:
  可以考虑枚举所有方案进行搜索,然后加一个最优性剪枝
  首先,尽量将顺子处理完毕,因为顺子能够一次性解决掉大量的牌
  处理完顺子之后,就只剩下一些面值不连续的单张,对子,单张,炸弹。设xyzw分别表示处理完顺子之后还剩下一张牌的面值数量,两张牌的……四张牌的数量
  那么我们可以对这些散牌进行贪心求解:
  首先进行四带二对,其次四带二单,再其次四带一对,随后是炸弹,紧跟着三带一对,三带一,三不带
  当z=0w=0时,那么就可以用x+y次操作将单牌和对子打光
  但是在贪心的时候,可能会遇到一些问题,所以最好使用DP求解
  然后我们来看加强版:
  加强版与普通版的不同之处在于两个王不能当对子牌打出去,即没有三带两王这种出发,其他情况就差不多,因为两张王可以打出火箭,而四带一对王就是四带二单,所以只需要打一个标记:如果两张王均出现了,并且在三带二时对子牌只剩一对了,那就不能带
  接下来就是原版斗地主:
  原版斗地主出自一位NOI选手,是2014年联赛模拟题。基本题意类似,不同之处在于没有四带二单或四带二对,新增连三带一,即飞机+翅膀,连三带一中可以包含2或者大小王,但这些只能当做每个三带一中的“另外一张牌”:QQQKKKAAA222是一个合法的连三带一,但只能当做是QQQ2+KKK2+AAA2
  
  所以我们可以将两张王拆开(如果两张王都有),然后看做单张牌(方便带),在最后如果还剩下两张以上的单牌,那么就x2y+1
  然后一个重点就是怎么处理连三带一中带的牌,有两种做法:
  ①.留到最后,用一个变量记录需要带多少张牌,在最后处理散牌时,按单牌,对子,三张,炸弹的顺序去填补要带的牌
  ②.在找到连三不带之后,另开一个函数,去枚举带哪些牌,注意一些细节和剪枝,也可以很快出解

复杂度

O(不知道)

代码
普通版:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#define rg register
using namespace std;
bool tre,twi,onc;
int p[20];
int T,n,ans,zzl;
void cal(rg int step,rg int x,rg int y,rg int z,rg int w)
{
    if( step>ans || step+z+w>ans )   return ;
    if( !z && !w ) {   ans=min( ans,step+x+y );return ; }
    zzl++;
    if( w>0 )
    {
        if( x>=2 )   cal( step+1,x-2,y,z,w-1 );
        if( y>=2 )   cal( step+1,x,y-2,z,w-1 );
        if( y>=1 )   cal( step+1,x,y-1,z,w-1 );
        if( z>=1 )   cal( step+1,x+1,y,z-1,w-1 );
        if( w>=2 )   cal( step+1,x,y,z,w-2 ),cal( step+1,x,y+1,z,w-2 );
        cal( step+1,x,y,z,w-1 );
        return ;
    }
    if( z>0 )
    {
        if( w>=1 )   cal( step+1,x,y+1,z-1,w-1 );
        if( x>=1 )   cal( step+1,x-1,y,z-1,w );
        else
            if( y>=1 )   cal( step+1,x,y-1,z-1,w );
            else   ans=min( ans,step+z );
        return ;
    }
}
void dfs(int step)
{
    if( step>ans )   return ;
    if( tre )
    {
        for(int i=3,j;i<=13;i++)
            if( p[i]>=3 )
            {
                for(j=i+1;j<=14;j++)   if( p[j]<=2 )   break ;
                if( j-i>=2 )
                    for(int l=i;l+1<=j-1;l++)
                        for(int r=l+1;r<=j-1;r++)
                        {
                            for(int h=l;h<=r;h++)   p[h]-=3;
                            dfs( step+1 );
                            for(int h=l;h<=r;h++)   p[h]+=3;
                        }
                i=j;
            }
    }
    if( twi )
    {
        for(int i=3,j;i<=13;i++)
            if( p[i]>=2 )
            {
                for(j=i+1;j<=14;j++)   if( p[j]<=1 )   break ;
                if( j-i>=3 )
                    for(int l=i;l+2<=j-1;l++)
                        for(int r=l+2;r<=j-1;r++)
                        {
                            for(int h=l;h<=r;h++)   p[h]-=2;
                            dfs( step+1 );
                            for(int h=l;h<=r;h++)   p[h]+=2;
                        }
                i=j;
            }
    }
    if( onc )
    {
        for(int i=3,j;i<=13;i++)
            if( p[i]>=1 )
            {
                for(j=i+1;j<=14;j++)   if( !p[j] )   break ;
                if( j-i>=5 )
                    for(int k=i;k+4<=j-1;k++)
                        for(int l=k+4;l<=j-1;l++)
                        {
                            for(int h=k;h<=l;h++)   p[h]-=1;
                            dfs( step+1 );
                            for(int h=k;h<=l;h++)   p[h]+=1;
                        }
                i=j;
            }
    }
    if( step>ans )   return;
    int x,y,z,w;
    x=y=z=w=0;
    for(int i=0;i<=14;i++)
        if( p[i] )
        {
            if( p[i]==1 )   x++;
            if( p[i]==2 )   y++;
            if( p[i]==3 )   z++;
            if( p[i]==4 )   w++;
        }
    cal( step,x,y,z,w );
}
int main()
{
    int x,y;
    scanf("%d%d",&T,&n);
    while( T-- )
    {
        memset( p,0x0,sizeof p );
        ans=n;tre=twi=onc=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&y,&x);
            if( y==1 )   y=14;
            p[y]++;
        }
        for(int i=3,j;i<=13;i++)
            if( p[i]>=3 )
            {
                for(j=i+1;j<=14;j++)   if( p[j]<3 )   break ;
                if( j-i>=2 )   tre=1;
            }
        for(int i=3,j;i<=13;i++)
            if( p[i]>=2 )
            {
                for(j=i+1;j<=14;j++)   if( p[j]<2 )   break ;
                if( j-i>=3 )   twi=1;
            }
        for(int i=3,j;i<=13;i++)
            if( p[i]>=1 )
            {
                for(j=i+1;j<=14;j++)   if( !p[j] )   break ;
                if( j-i>=5 )   onc=1;
            }
        dfs( 0 );
        printf("%d\n",ans);
    }
    return 0;
}

加强版:没有,可以按着上面改
原版:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#define Rint register int
using namespace std;
bool tre,twi,onc,tre_1;
bool flag;
int p[30],use[30];
int ans;
void cal(Rint step,Rint x,Rint y,Rint z,Rint w)
{
    if( step+z+w>=ans )   return ;
    if( !z && !w )
    {
        if( x>=2 && flag )   x--;//大小王当对子牌
        ans=min( ans,step+x+y );return ;//单牌+对子打完
    }
    if( w>0 )   cal( step+1,x,y,z,w-1 );//炸弹(三带一)
    if( z>0 )
    {
        if( x>0 )   cal( step+1,x-1,y,z-1,w );//三带一
        if( y>0 )   cal( step+1,x,y-1,z-1,w );//三带二
        if( !x && !y )   ans=min( ans,step+z+w );//三不带+炸弹打完
    }
}
void dfs(Rint step);
void work(Rint k,Rint sum,Rint step);
int main()
{
    freopen("landlords.in","r",stdin);
    freopen("landlords.out","w",stdout);
    char s[20];
    while( scanf("%s",s)!=EOF )
    {
        ans=17;
        tre_1=tre=twi=onc=0;
        for(int i=0;i<=16;i++)   p[i]=0;
        for(int i=0,x;i<=16;i++)
        {
            if( s[i]>='2' && s[i]<='9' )   x=s[i]-'0';
            else   if( s[i]=='0' )   x=10;
            else   if( s[i]=='J' )   x=11;
            else   if( s[i]=='Q' )   x=12;
            else   if( s[i]=='K' )   x=13;
            else   if( s[i]=='A' )   x=14;
            else   if( s[i]=='*' )   x=15;
            else   if( s[i]=='#' )   x=16;
            p[x]++;
        }
        flag=p[15]&p[16];
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<3 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<3 )   break ;
            if( j-i>=2 )   tre_1=1;
        }   
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<3 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<3 )   break ;
            if( j-i>=2 )   tre=1;
        }
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<2 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<2 )   break ;
            if( j-i>=3 )   twi=1;
        }
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<1 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<1 )   break ;
            if( j-i>=5 )   onc=1;
        }
        dfs( 0 );
        printf("%d\n",ans);
    }
    return 0;
}
void work(Rint k,Rint sum,Rint step)
{
    if( k>16 )   return ;
    if( !sum )   { dfs( step+1 );return ; }
    if( sum>=p[k] )
    {
        int tmp=p[k];
        p[k]=0;
        work( k+1,sum-tmp,step );
        p[k]=tmp;
    }
    else
    {
        p[k]-=sum;
        work( k+1,0,step );
        p[k]+=sum;
    }
    work( k+1,sum,step );
}
void dfs(Rint step)
{
    if( step>=ans )   return ;
    if( tre_1 )//连三带一
    {
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<3 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<3 )   break ;
            if( j-i>=2 )
            {
                for(int k=i;k+1<=j-1;k++)
                    for(int l=k+1;l<=j-1;l++)
                    {
                        for(int m=k;m<=l;m++)   p[m]-=3;
                        work( 2,l-k+1,step );
                        for(int m=k;m<=l;m++)   p[m]+=3;
                    }
            }
            i=j;
        }
    }
    if( step>=ans )   return;
    if( tre )//三顺子
    {
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<3 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<3 )   break ;
            if( j-i>=2 )
            {
                for(int k=i;k+1<=j-1;k++)
                    for(int l=k+1;l<=j-1;l++)
                    {
                        for(int m=k;m<=l;m++)   p[m]-=3;
                        dfs( step+1 );
                        for(int m=k;m<=l;m++)   p[m]+=3;
                    }
            }
            i=j;
        }
    }
    if( step>=ans )   return;
    if( twi )//双顺子
    {
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<2 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<2 )   break ;
            if( j-i>=3 )
            {
                for(int k=i;k+2<=j-1;k++)
                    for(int l=k+2;l<=j-1;l++)
                    {
                        for(int m=k;m<=l;m++)   p[m]-=2;
                        dfs( step+1 );
                        for(int m=k;m<=l;m++)   p[m]+=2;
                    }
            }
            i=j;
        }
    }
    if( step>=ans )   return;
    if( onc )//单顺子
    {
        for(int i=3,j;i<=13;i++)
        {
            if( p[i]<1 )   continue ;
            for(j=i+1;j<=14;j++)   if( p[j]<1 )   break ;
            if( j-i>=5 )
            {
                for(int k=i;k+4<=j-1;k++)
                    for(int l=k+4;l<=j-1;l++)
                    {
                        for(int m=k;m<=l;m++)   p[m]--;
                        dfs( step+1 );
                        for(int m=k;m<=l;m++)   p[m]++;
                    }
            }
            i=j;
        }
    }
    if( step>=ans )   return;
    int x,y,z,w;
    x=y=z=w=0;
    for(int i=2;i<=16;i++)
    {
        if( p[i]==1 )   x++;
        if( p[i]==2 )   y++;
        if( p[i]==3 )   z++;
        if( p[i]==4 )   w++;
    }
    cal( step,x,y,z,w );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值