Luogu P2540 NOIP2015提高组 斗地主 加强版 题解 [ 紫 ] [ 深搜 ] [ 剪枝 ]

斗地主:一步一步推性质就能做出来的剪枝题。

这题思路和小木棒的剪枝思路极其相似,剪枝的角度都差不多。

其实大部分搜索剪枝题都是先观察性质,列出性质后选择几个比较关键且代码好写的性质进行剪枝,特别要注意避免重复搜索相同状态的剪枝。同时注意想好了之后再写代码。

并且大部分搜索题会把正向的搜索树 hack 得很大,当被卡的时候不妨试试倒着搜索,可能有奇效。

思路

观察题目,我们可以发现如下几点性质:

  • 出牌的顺序不影响最终的结果,先出某一组牌和后出某一组牌本质是一样的。
  • 最后只剩下单牌、对子、三张牌、炸弹和王炸的时候,我们可以贪心地扫一遍统计至少存在一张牌的码数的个数,特判掉大小王得出答案。
  • 在顺子和带牌中,顺子打出的方案数一般比带牌的方案数更少。

因此,我们可以设计出如下剪枝方案:

  • 将搜索阶段拆解为 6 步,依次完成。分别是三顺子、双顺子、单顺子、四带二、三带一和二、最后剩下的统一出掉,大王小王绑一起出。
  • 记录下当前的搜索阶段 p,以及上一次在同阶段最后搜索到的牌 lst。每次接着 lst 后面来搜索,避免搜到重复状态。
  • 把三顺子、双顺子、单顺子、四带二、三带一和二全部出完之后,我们可以线性扫一遍算出最终的答案,避免了搜索多余状态。

实现的时候对每个搜索阶段分步实现,代码在写顺子和带牌内部时可以稍作修改之后重复利用。

时间复杂度玄学,但是能过大部分数据。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
using pi=pair<int,int>;
/*
三顺子 1
双顺子 2
单顺子 3
四带二 4
三带一、二 5
最后剩下的统一出掉,大王小王绑一起出。 
1~11 3~K
12 A
13 2
14 little
15 big
*/
int tot[20],ans=0x3f3f3f3f,now=0,n;
void dfs(int p,int lst)
{
	if(p>=6)
	{
		int cur=0;
		for(int i=1;i<=15;i++)if(tot[i])cur++;
		if(tot[14]&&tot[15])cur--;
		ans=min(ans,now+cur);
		return;
	}
	if(p==1)
	{
		int pre=0;
		for(int i=1;i<=12;i++)
		{
			if(tot[i]>=3)pre++;
			else pre=0;
			if(pre>=2&&i>=lst)
			{
				int tmp[20];
				memcpy(tmp,tot,sizeof(tmp));
				tot[i]-=3;
				for(int j=2;j<=pre;j++)
				{
					tot[i-j+1]-=3;
					now++;
					dfs(p,i);
					now--;
				}
				memcpy(tot,tmp,sizeof(tmp));
			}
		}
		dfs(p+1,1);
		return;
	}
	if(p==2)
	{
		int pre=0;
		for(int i=1;i<=12;i++)
		{
			if(tot[i]>=2)pre++;
			else pre=0;
			if(pre>=3&&i>=lst)
			{
				int tmp[20];
				memcpy(tmp,tot,sizeof(tmp));
				tot[i]-=2;
				tot[i-1]-=2;
				for(int j=3;j<=pre;j++)
				{
					tot[i-j+1]-=2;
					now++;
					dfs(p,i);
					now--;
				}
				memcpy(tot,tmp,sizeof(tmp));
			}
		}
		dfs(p+1,1);
		return;		
	}
	if(p==3)
	{
		int pre=0;
		for(int i=1;i<=12;i++)
		{
			if(tot[i]>=1)pre++;
			else pre=0;
			if(pre>=5&&i>=lst)
			{
				int tmp[20];
				memcpy(tmp,tot,sizeof(tmp));
				tot[i]-=1;
				tot[i-1]-=1;
				tot[i-2]-=1;
				tot[i-3]-=1;
				for(int j=5;j<=pre&&i-j+1>=1;j++)
				{
					tot[i-j+1]-=1;
					now++;
					dfs(p,i);
					now--;
				}
				memcpy(tot,tmp,sizeof(tmp));
			}
		}
		dfs(p+1,1);
		return;		
	}
	if(p==4)
	{
		for(int i=1;i<=13;i++)
		{
			if(tot[i]>=4)
			{
				tot[i]-=4;
				for(int j=1;j<=15;j++)
				{
					if(tot[j]<=0)continue;
					for(int k=1;k<=15;k++)
					{
						if(tot[k]<=0)continue;
						if(tot[j]&&tot[k]&&(j!=k||tot[j]>=2))
						{
							tot[j]--;
							tot[k]--;
							now++;
							dfs(p,i);
							tot[j]++;
							tot[k]++;
							now--;
						}
						if(tot[j]>=2&&tot[k]>=2&&(j!=k||tot[j]>=4))
						{
							tot[j]-=2;
							tot[k]-=2;
							now++;
							dfs(p,i);
							tot[j]+=2;
							tot[k]+=2;
							now--;
						}			
					}
				}
				tot[i]+=4;
			}
		}
		dfs(p+1,1);
		return;
	}
	if(p==5)
	{
		for(int i=1;i<=13;i++)
		{
			if(tot[i]>=3)
			{
				tot[i]-=3;
				for(int j=1;j<=15;j++)
				{
					if(tot[j]>=1)
					{
						tot[j]--;
						now++;
						dfs(p,i);
						tot[j]++;
						now--;
					}
					if(tot[j]>=2)
					{
						tot[j]-=2;
						now++;
						dfs(p,i);
						tot[j]+=2;
						now--;
					}
				}
				tot[i]+=3;
			}
		}
		dfs(p+1,1);
		return;
	}
}
void solve()
{
	memset(tot,0,sizeof(tot));
	ans=0x3f3f3f3f;
	now=0;
	for(int i=1;i<=n;i++)
	{
		int a,b;
		cin>>a>>b;
		if(3<=a&&a<=13)tot[a-2]++;
		else if(a<3&&a>0)tot[a+11]++;
		else tot[b+13]++;
	}
	dfs(1,1);
	cout<<ans<<'\n';
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t>>n;
	while(t--)solve();
	return 0;
}
posted @   KS_Fszha  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示