牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下:3<4<5<6<7<8<9<10< J< Q< K< A<2<小王<大王 而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 n 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:
输入格式
第一行包含用空格隔开的2个正整数 T,n表示手牌的组数以及每组手牌的张数。
接下来 T组数据,每组数据 n 行,每行一个非负整数对 ai,bi,表示一张牌,其中 ai表示牌的数码, bi 表示牌的花色,中间用空格隔开。特别的,我们用 11 来表示数码 A, 11 表示数码 J, 12 表示数码 Q, 13 表示数码 K;黑桃、红心、梅花、方片分别用 1-4 来表示;小王的表示方法为 0 1 ,大王的表示方法为 0 2 。
输出格式
共 T 行,每行一个整数,表示打光第 i 组手牌的最少次数。
这绝对不是一个爆搜就完了!这绝对不是一个爆搜就完了!这绝对不是一个爆搜就完了!重说三。
首先….这个题可以是个爆搜或者DP,反正DP我不会。
这个题DFS BFS都可以…..昨天下午我刚了一下午+半晚上….370行最后以30分滚粗….
那么…这个毒瘤爆搜….该怎么做呢?
嗯,来战吧!
首先,这个题我们可以选择用DFS+BFS的结合,因为用BFS的话每次扔十几张牌…爆了队列怎么办啊…DFS则有可能重复搜到之前的状态。
我选择把A,2,王设置成,14,15,,16
其实我今天学到的做法是DFS+状压判重。
把DFS搜到的每一个状态,压缩成一个整数,存到map里面。
233333这是不是利用了hash的思想?
我们读入的时候,开一个pk数组,记录一下每张牌有多少张,大王小王算同一张。
进了DFS,把每一个状态压缩成一个数字储存在map里。
对于每一种出牌方式,我们怎么搞呢?
炸弹&&对子牌&&王炸&&三张 :
for一遍,扫到4/3/2张(及以上)的就把这个位置的牌-4/3/2,进入下一层DFS,然后回溯+4/3/2。
三带一 三张码数相同的牌 + 一张单牌:
for一遍。扫到3张及以上的,当前位置-3,再加一层for,如果这个位置有牌就–。进DFS然后回溯。
三带二 三张码数相同的牌 + 一对牌:
for一遍。扫到3张及以上的,当前位置-3,再加一层for,如果这个位置牌大于等于2就-2。进DFS然后回溯。
四带二 四张码数相同的牌+任意两张单牌(或任意两对牌):
for一遍。扫到四张的,当前位置-4,加一层for,扫到一个位置有2/1及以上的就-2/1,再加一层for,对应扫到一个位置有2/1及以上的就-2/1,然后回溯。
单顺子 五张或更多码数连续的单牌(不包括 2 点和双王):
for的时候要注意2和王,如果当前往后面4个位置都有牌,就说明连成顺子,从这个位置往后搜,如果没有牌直接break,有牌进DFS,记录当前位置,
一次结束的时候从进入位置到当前位置++。
双顺子 三对或更多码数连续的对牌(不包括 2 点和双王)
同上。
三顺子 二个或更多码数连续的三张牌(不能包括 2 点和双王)
同上。
#include
#include
#include
#include
#define fk puts("hahahs");
#define mem(a) memset(a,0,sizeof(a));
using namespace std;
typedef unsigned long long ll;
const int maxn=50;
int n;
int pk[maxn];
mapint >vis;
ll ys()
{
ll hah=0;
for(int i=3;i<=16;i++)
hah=hah*6+pk[i];
return hah;
}
int dfs()
{
int ans=30;
ll zt=ys();
if(zt==0)
return 0;
if(vis.count(zt))
return vis[zt];
bool flag=0;
//三顺子
for(int i=3;i<=13;i++)
if(pk[i]>=3&pk[i+1]>=3)
{
pk[i]-=3,pk[i+1]-=3;
ans=min(ans,dfs()+1);
flag=1;
int j;
for(j=i+2;j<=14;j++)
{
if(pk[j]<3)
{
break;
}
else
{
pk[j]-=3;
ans=min(ans,dfs()+1);
}
}
for(int k=i;k3;
}
//双顺子
for(int i=3;i<=12;i++)
if(pk[i]>=2&&pk[i+1]>=2&&pk[i+2]>=2)
{
pk[i]-=2;pk[i+1]-=2;pk[i+2]-=2;
flag=1;
ans=min(ans,dfs()+1);
int j;
for(j=i+3;j<=14;j++)
{
if(pk[j]<2)
{
break;
}
else
{
pk[j]-=2;
ans=min(ans,dfs()+1);
}
}
for(int k=i;k2;
}
//单顺子
for(int i=3;i<=10;i++)
if(pk[i]&&pk[i+1]&&pk[i+2]&&pk[i+3]&&pk[i+4])
{
pk[i]--;pk[i+1]--;pk[i+2]--;pk[i+3]--;pk[i+4]--;
flag=1;
ans=min(ans,dfs()+1);
int j;
for(j=i+5;j<=14;j++)
{
if(!pk[j])
break;
else
{
pk[j]--;
ans=min(ans,dfs()+1);
}
}
for(int k=i;k//四带二
for(int i=3;i<16;i++)
if(pk[i]==4)
{
pk[i]-=4;
for(int j=3;j<16;j++)
{
if(pk[j]>=2)
{
pk[j]-=2;
for(int k=3;k<16;k++)
{
if(pk[k]>=2)
{
pk[k]-=2;
flag=1;
ans=min(ans,dfs()+1);
pk[k]+=2;
}
}
pk[j]+=2;
}
}//一对
for(int j=3;j<=16;j++)
{
if(pk[j])
{
pk[j]--;
for(int k=3;k<=16;k++)
{
if(pk[k])
{
pk[k]--;
flag=1;
ans=min(ans,dfs()+1);
pk[k]++;
}
}
pk[j]++;
}
}//俩单
pk[i]+=4;
}
//三带二
for(int i=3;i<16;i++)
if(pk[i]>=3)
{
pk[i]-=3;
for(int j=3;j<=16;j++)
{
if(pk[j]>=2)
{
flag=1;
pk[j]-=2;
ans=min(ans,dfs()+1);
pk[j]+=2;
}
}
pk[i]+=3;
}
//三带一
for(int i=3;i<16;i++)
if(pk[i]>=3)
{
pk[i]-=3;
for(int j=3;j<=16;j++)
{
if(pk[j])
{
flag=1;
pk[j]--;
ans=min(ans,dfs()+1);
pk[j]++;
}
}
pk[i]+=3;
}
//炸弹
for(int i=3;i<16;i++)
if(pk[i]==4)
{
pk[i]-=4;
ans=min(ans,dfs()+1);
flag=1;
pk[i]+=4;
}
//三单
for(int i=3;i<16;i++)
if(pk[i]>=3)
{
pk[i]-=3;
ans=min(ans,dfs()+1);
flag=1;
pk[i]+=3;
}
//对牌
for(int i=3;i<=16;i++)
{
if(pk[i]>=2)
{
pk[i]-=2;
ans=min(ans,dfs()+1);
flag=1;
pk[i]+=2;
}
}
//单牌
if(!flag)
{
ans=0;
for(int i=3;i<=16;i++)
ans+=pk[i];
}
vis[zt]=ans;
// printf("%d\n",ans);
return ans;
}
int main()
{
freopen("landlords.in","r",stdin);
freopen("landlords.out","w",stdout);
int t;
scanf("%d%d",&t,&n);
while(t--)
{
mem(pk);
for(int i=1;i<=n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
if(!a)
pk[16]++;
else if(a>=3)
pk[a]++;
else
pk[a+13]++;
}
printf("%d\n",dfs());
vis.clear();
}
return 0;
}