第一篇Blog交给一道大搜索……作为NOIP2015Day1的最后一题斗地主并不难想出做法,但是写起来太麻烦了……很多人一看见题目就失去了做这道题的勇气,其实这个题本身还是挺好写的,只是代码相对长许多就是了。
题目要求输入扑克牌张数以及扑克花色与大小,输出最少需要多少步打完手中的牌,那么首先要保证要从每次能出牌的最多张数开始搜索。但是我们能注意到他有三顺有二顺有单顺,那么只是贪心肯定是不行的肯定会错失最优解(例如对于对3、对4、对5、对6、对7以及单8、单9、单10的牌型如果只是一昧出最多牌肯定先打双顺却忽视了拆牌这一说)。所以我们要从三顺开始找,找出从第i种牌开始所能打出的顺子的长度,再从最短开始一个一个搜索回溯找出最优解。
找完了顺子再该怎么办呢?这时候我们发现可能仍有遗留下来的三张以致四张,所以要再判断有没有三张以上的同种牌,没有的话就可以之间加上当前牌的种类总数判断是否更优跳出循环了。如果有,那么我们再分别判断四张、四带一对、四带两对、四带两单以及三带一、三带一对的情况。剩下的零星的单和对子,我们便可以判断一下加上出它们的次数后是否更优后退出了。
其实这个题想骗部分分特别容易……比如对于前6个点N≤4,不存在任何顺子的情况,如果有三张及以上同样的牌直接输出1就能过,没有的话就输出牌种类数也过了……以及关于牌的花色,题目说了对于斗地主花色不起任何作用,所以十有八九只是卡读入的(臆测)……
下面拍出我的代码,略长略难懂见谅
#include<cstdio>
#define A 14
//这里我为了搜索顺子方便把1也就是A重定义成了14
using namespace std;
int t,N,ans,n;
int a[15];//记录当前手牌张数
int job(int m)//寻找只有4张以下的手牌时最少出牌次数
{
int i,x=0;
for(i=0;i<=14;i++)
if(a[i])
if(a[i]>=3)
return m+1;
else
x++;
return m+x;
}
void dfs(int j,int m)
{
int i,k,h;
if(!m||j>ans)//剪枝,节省时间
{
if(ans>j)
ans=j;
return;
}
if(m<=4)//同样也是剪枝,因为只剩4张牌以下时情况比较好判断
{
j=job(j);
if(ans>j)
ans=j;
return;
}
for(i=3;i<A;i++)
{
if(a[i]>=3&&a[i+1]>=3)//判断三顺
{
int x=2;//最短长度
for(k=i+2;k<=A;k++)
if(a[k]<3)
break;
else
x++;
for(k=2;k<=x;k++)
{
for(h=0;h<k;h++)
a[i+h]-=3;
dfs(j+1,m-k*3);
for(h=0;h<k;h++)
a[i+h]+=3;
}
}
if(a[i]>=2&&a[i+1]>=2&&a[i+2]>=2)//判断二顺
{
int x=3;//最短长度
for(k=i+3;k<=A;k++)
if(a[k]<2)
break;
else
x++;
for(k=3;k<=x;k++)
{
for(h=0;h<k;h++)
a[i+h]-=2;
dfs(j+1,m-k*2);
for(h=0;h<k;h++)
a[i+h]+=2;
}
}
if(a[i]&&a[i+1]&&a[i+2]&&a[i+3]&&a[i+4])//判断单顺
{
int x=5;//最短长度
for(k=i+5;k<=A;k++)
if(!a[k])
break;
else
x++;
for(k=5;k<=x;k++)
{
for(h=0;h<k;h++)
a[i+h]--;
dfs(j+1,m-k);
for(h=0;h<k;h++)
a[i+h]++;
}
}
}
bool b=1;
int number=0;
for(i=0;i<=A;i++)//寻找是否有三张以上的同种牌
if(a[i]>=3)
{
b=0;
break;
}
else
if(a[i])
number++;
if(b==1)//没有三张以上的同种牌,直接在次数上加上剩余牌的种类数,结束搜索
if(ans>j+number)
{
ans=j+number;
return;
}
else
return;
for(i=2;i<=A;i++)
{
if(a[i]==4)//判断四张
{
int x=15,y=15;
for(k=0;k<=A;k++)
{
if(a[k]==2)
if(x<15)//四带两对
{
a[i]-=4;
a[k]-=2;
a[x]-=2;
dfs(j+1,m-8);
a[i]+=4;
a[k]+=2;
a[x]+=2;
break;
}
else//四带一对
{
x=k;
a[i]-=4;
a[k]-=2;
dfs(j+1,m-6);
a[i]+=4;
a[k]+=2;
}
else
if(a[k]==1)//四带两单
{
if(y<15)
{
a[i]-=4;
a[k]--;
a[y]--;
dfs(j+1,m-6);
a[i]+=4;
a[k]++;
a[y]++;
break;
}
else
y=k;
}
else//炸弹,裸四张
{
a[i]-=4;
dfs(j+1,m-4);
a[i]+=4;
}
}
}
if(a[i]==3)
{
for(k=0;k<=14;k++)
{
if(a[k]==1)//三带单张
{
a[i]-=3;
a[k]--;
dfs(j+1,m-4);
a[i]+=3;
a[k]++;
break;
}
if(a[k]==2)//三带一对
{
a[i]-=3;
a[k]-=2;
dfs(j+1,m-5);
a[i]+=3;
a[k]+=2;
break;
}
}
}
}
}
void work()//处理每组数据
{
int i,j,k;
n=N;
for(i=0;i<=14;i++)
a[i]=0;
ans=0;
for(i=1;i<=n;i++)
{
scanf("%d%d",&k,&j);
if(k==1)
a[14]++;
else
a[k]++;
}
for(i=0;i<=14;i++)
if(a[i])
if(a[i]>=3&&n<=4)//n≤4时三张以上直接输出1;
{
printf("1\n");
return;
}
else
ans++;
if(n<=4)
printf("%d\n",ans);
else
{
dfs(0,n);
printf("%d\n",ans);
}
return;
}
int main()//读入组数、牌数
{
int i;
scanf("%d%d",&t,&N);
for(i=1;i<=t;i++)
work();
return 0;
}