当前位置: 代码迷 >> 综合 >> 【NOIP2015】斗地主(及加强版原版)
  详细解决方案

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

热度:46   发布时间:2023-12-05 12:41:33.0

题意

??给定一副牌,保证合法(即牌包括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
??
??所以我们可以将两张王拆开(如果两张王都有),然后看做单张牌(方便带),在最后如果还剩下两张以上的单牌,那么就x?2y+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 );elseif( 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 );
}