luoguP2540orP2668斗地主(noip2015)

题目如下:
https://www.luogu.org/problemnew/show/P2540

非常繁锁的题干令人慌乱,不知如何下手。
那么静下心来分析这道题;
问题:
手里一套扑克牌,如何规划出牌策略,以最快的次数将其打完?

考虑的因素太多太多,再加上顺子这一恶心的规则,没有斗地主千场以上经验的人真的不知所措。
解决方案(思路):
每当人脑无法想象如何处理的时候,就自然而然的想到暴力搜索,顺子当然是好搜的,按照牌的顺序就行,可是其他组合如何考虑?

由于其他种类的方案是死的不像顺子那样复杂多变,而且仔细想想是有递推关系的。
比如手里有4个A,三个3,那么当前将牌出完的次数可以由 手里只有三个3递推过来(很明显,先出三个三,再出四个A),还可以由 手里有 一个三递推(先出一个3,再出四带二),等等;
所以显然可以用dp来计算出不管顺子的情况,正常出牌每种方案出完牌的最少次数。

其中需要注意的是,不仅能出牌还能拆牌
如四个A可以当一个炸弹出,也可以拆成3个A,和一个A,还可拆成两个对A或四个A,具体如下
dp[i][j][k][l]表示有i个炸弹,j个三张牌的,k个对子,l个单牌的情况将牌打完的最小次数

void DPinit(){
	memset(dp,0x3f,sizeof(dp));
	dp[0][0][0][0]=0;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			for(int k=0;k<=n;k++){
				for(int l=0;l<=n;l++){
					if(i){
						int minn=INF; 
						if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);//四带两个对子 
						if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);//四带二不同单牌 
						if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-1][l]+1);//四带两个相同的单牌,如AAAA+33 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j+1][k][l+1]);//四拆成三和一 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k+2][l]);//四拆成两个对子
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l+4]);//四拆成四个单 
						if(i>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-2][j][k][l]+1);//四带两个一样的对子  如AAAA+33+33 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1);//炸弹甩 
					}
					if(j){
						if(l>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);//三带一 
						if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);//三带二 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1);//三直接甩
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k+1][l+1]);//三拆对子+单 
					}
					if(k){
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l+2]);//对子拆成两单;
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1);//出对子 
					}
					if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1);//单 
				}
			}
		}
	}
}

那么处理完不带顺子的情况,我们就可以轻松地只搜索顺子,操作如下
1.ans=当前状况不用顺子的情况所需次数。(王牌特殊处理一下,就当做单张出或者当对子)
2.枚举顺子的情况,更新ans,逐步;

代码如下

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define INF 99999999
#include<algorithm>
using namespace std;
int p[20],shun[5]={0,5,3,2},T,n,sumking,dp[25][25][25][25],sum[5],ans;
void DPinit(){
	memset(dp,0x3f,sizeof(dp));
	dp[0][0][0][0]=0;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			for(int k=0;k<=n;k++){
				for(int l=0;l<=n;l++){
					if(i){
						int minn=INF; 
						if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);//四带两个对子 
						if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);//四带二不同单牌 
						if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-1][l]+1);//四带两个相同的单牌,如AAAA+33 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j+1][k][l+1]);//四拆成三和一 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k+2][l]);//四拆成两个对子
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l+4]);//四拆成四个单 
						if(i>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-2][j][k][l]+1);//四带两个一样的对子  如AAAA+33+33 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1);//炸弹甩 
					}
					if(j){
						if(l>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);//三带一 
						if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);//三带二 
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1);//三直接甩
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k+1][l+1]);//三拆对子+单 
					}
					if(k){
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l+2]);//对子拆成两单;
						dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1);//出对子 
					}
					if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1);//单 
				}
			}
		}
	}
}
int check(int t1,int t2,int t3,int t4,int sumk){
	if(sumk==2) return min(dp[t4][t3][t2][t1]+1,dp[t4][t3][t2][t1+2]);
	else return sumk==1?dp[t4][t3][t2][t1+1]:dp[t4][t3][t2][t1];
}
void dfs(int step){
	if(step>=ans) return;
	memset(sum,0,sizeof(sum));
	for(int i=2;i<=14;i++) sum[p[i]]++;
	ans=min(ans,step+check(sum[1],sum[2],sum[3],sum[4],sumking));
	for(int k=1;k<=3;k++){
		for(int i=3;i<=14;i++){
			int j;
			for(j=i;j<=14&&p[j]>=k;j++){
				p[j]-=k;
				if(j-i+1>=shun[k]) dfs(step+1);
			}
			for(j--;j>=i;j--) p[j]+=k;
		}
	}
}
int main(){
	scanf("%d%d",&T,&n);
	DPinit(); 
	while(T--){
		memset(p,0,sizeof(p));
		sumking=0;
		for(int i=1;i<=n;i++){
			int siz,col; scanf("%d%d",&siz,&col);
			if(siz==0) sumking++;
			else if(siz==1) p[14]++;
			else p[siz]++;
		}
		ans=INF;
		dfs(0);
		printf("%d\n",ans);
	}
	return 0;
} 

IGNB!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值