【NOIP2015TG D1T3】斗地主

第一篇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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值