题意
给定一副牌,保证合法(即牌包括2,3,4,5,6,7,8,9,10,J,Q,K,A,王,并且每种牌不超过四张,王不超过两张),给定合法的出牌方案,求最少出牌次数。
方案:
①.火箭,即双王
②.炸弹,即4张面值相同的牌
③.单张和对子
④.三不带,即3张面值相同的牌
⑤.三带一单或三带一对
⑥.单顺子,即面值连续的五张以上的牌
⑦.双顺子,基本同上
⑧.三顺子,基本同上
⑨.四带二单(包括四带一对)或四带二对(包括四带四,例如44445555就可以一次打出)
解法
搜索:
可以考虑枚举所有方案进行搜索,然后加一个最优性剪枝
首先,尽量将顺子处理完毕,因为顺子能够一次性解决掉大量的牌
处理完顺子之后,就只剩下一些面值不连续的单张,对子,单张,炸弹。设 x,y,z,w 分别表示处理完顺子之后还剩下一张牌的面值数量,两张牌的……四张牌的数量
那么我们可以对这些散牌进行贪心求解:
首先进行四带二对,其次四带二单,再其次四带一对,随后是炸弹,紧跟着三带一对,三带一,三不带
当 z=0,w=0 时,那么就可以用 x+y 次操作将单牌和对子打光
但是在贪心的时候,可能会遇到一些问题,所以最好使用 DP 求解
然后我们来看加强版:
加强版与普通版的不同之处在于两个王不能当对子牌打出去,即没有三带两王这种出发,其他情况就差不多,因为两张王可以打出火箭,而四带一对王就是四带二单,所以只需要打一个标记:如果两张王均出现了,并且在三带二时对子牌只剩一对了,那就不能带
接下来就是原版斗地主:
原版斗地主出自一位 NOI 选手,是2014年联赛模拟题。基本题意类似,不同之处在于没有四带二单或四带二对,新增连三带一,即飞机+翅膀,连三带一中可以包含2或者大小王,但这些只能当做每个三带一中的“另外一张牌”: QQQKKKAAA222 是一个合法的连三带一,但只能当做是 QQQ2+KKK2+AAA2 。
所以我们可以将两张王拆开(如果两张王都有),然后看做单张牌(方便带),在最后如果还剩下两张以上的单牌,那么就 x−2 , y+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 );
}