斗地主
题目描述:
牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、 方片的 A 到 K 加上大小王的共 54 张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 n 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。
具体规则如下:
解:
模拟+爆搜:
如果每一步直接搜的话显然太慢了:
于是可以贪心搜顺子:
1.A可以接在K后,输入时直接把A接在K后; 大王小王当对出 然后把其他的位置减一下即可:
输入的时候这样乱搞:
int x=read(),y=read();
if(x==1)x=13;
else if(x)x--;
card[x]++;
2.每次找可以形成顺子的位置,然后尝试把顺子给出出去
先搜三顺子,再搜双顺子,再搜单顺子
搜顺子的过程(以三顺子为例):
int sanshunzi(int i)
{
int p=i;
while(card[p]>=3&&p<=13)p++;
if(p-i>=2)return p;
//返回可形成三顺子的末位置
return 0;//没有就返回0
}
3.顺子出完了之后然后开一个桶,设计一个calc函数, 贪心出散牌即可:
能出炸就出炸,炸没了就出三带n:
具体就像这样:
int calc()
{
memset(a,0,sizeof(a));
//开一个桶,把不同的装进去
for(int i=0;i<=13;i++)
++a[card[i]];
int tot=0;
while(a[4]&&a[2]>=2)--a[4],a[2]-=2,++tot;
//4带2个对
while(a[4]&&a[1]>=2)--a[4],a[1]-=2,++tot;
//4带2张牌
while(a[4]&&a[2])--a[4],--a[2],++tot;
//4带一个对
while(a[3]&&a[2])--a[3],--a[2],++tot;
//3带2
while(a[3]&&a[1])--a[3],--a[1],++tot;
//3带一
return tot+a[1]+a[2]+a[3]+a[4];
//不能带的直接出出去
}
4.最后dfs,答案即为打顺子步数+贪心出散牌步数.
注意:一定要注意多组数据!!!!!
(样例很坑)
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,t;
int a[21],card[21],ans=31;
inline int read()
{
int X=0,w=0;char ch=0;
while(!isdigit(ch))
{
w|=ch=='-';ch=getchar();
}
while(isdigit(ch))
{
X=X*10+ch-'0';ch=getchar();
}
return w?-X:X;
}
int danshunzi(int i)
{
int p=i;
while(card[p]>=1&&p<=13)p++;
if(p-i>=5)return p;
return 0;
}
int shuangshunzi(int i)
{
int p=i;
while(card[p]>=2&&p<=13)p++;
if(p-i>=3)return p;
return 0;
}
int sanshunzi(int i)
{
int p=i;
while(card[p]>=3&&p<=13)p++;
if(p-i>=2)return p;
//返回可形成三顺子的末位置
return 0;//没有就返回0
}
int calc()
{
memset(a,0,sizeof(a));
//开一个桶,把不同的装进去
for(int i=0;i<=13;i++)
++a[card[i]];
int tot=0;
while(a[4]&&a[2]>=2)--a[4],a[2]-=2,++tot;
//4带2个对
while(a[4]&&a[1]>=2)--a[4],a[1]-=2,++tot;
//4带2张牌
while(a[4]&&a[2])--a[4],--a[2],++tot;
//4带一个对
while(a[3]&&a[2])--a[3],--a[2],++tot;
//3带2
while(a[3]&&a[1])--a[3],--a[1],++tot;
//3带一
return tot+a[1]+a[2]+a[3]+a[4];
//不能带的直接出出去
}
void dfs(int step)
{
if(step>=ans)return;//最优性剪枝
int k=calc();//贪心出散牌的步数
ans=min(ans,step+k);
int pos;
for(int i=2;i<=13;i++)
{
if(sanshunzi(i))//3顺子
{
pos=sanshunzi(i);
for(int j=i+1;j<=pos-1;j++)
{
for(int v=i;v<=j;v++)
card[v]-=3;//枚举顺子
dfs(step+1);
for(int v=i;v<=j;v++)
card[v]+=3;//回溯
}
}
}
for(int i=2;i<=13;i++)
{
if(shuangshunzi(i))//双顺子
{
pos=shuangshunzi(i);
for(int j=i+2;j<=pos-1;j++)
{
for(int v=i;v<=j;v++)
card[v]-=2;
dfs(step+1);
for(int v=i;v<=j;v++)
card[v]+=2;
}
}
}
for(int i=2;i<=13;i++)
{
if(danshunzi(i))//单顺子
{
pos=danshunzi(i);
for(int j=i+4;j<=pos-1;j++)
{
for(int v=i;v<=j;v++)
card[v]--;
dfs(step+1);
for(int v=i;v<=j;v++)
card[v]++;
}
}
}
}
int main()
{
cin>>t>>n;
while(t--)
{
int x,y;
ans=31;
memset(card,0,sizeof(card));
for(int i=1;i<=n;i++)
{
int x=read(),y=read();
if(x==1)x=13;//输入时乱搞一下
else if(x)x--;
card[x]++;
}
dfs(0);
printf("%d\n",ans);
}
}