2015提高组 斗地主P2540 题解(超详细)

吐槽一下,为啥收钱协会越来越喜欢出大模拟呢[恼火]。

目录

分析:

1.顺子

2.其他牌种

3.拆牌

4.本题坑点

5.后记


分析:

虽然读入了花色,但是斗地主的玩法是不需要花色的,而且题目也说了,所以只需要记录点数即可。

1.顺子

然后我们可以发现:只有顺子是需要点数的,其他的直接出就可以了。

所以我们先把顺子用DFS枚举,然后再分析其他的牌。

注意:大小王不打火箭的话就可以作为不能用到顺子的单牌。

在判断顺子这里我用了一个小技巧:对于最小的顺子,连对(双顺子)和飞机(三顺子),牌数分别是:1\times 52\times 33\times 2,算出来分别是5,6,6。所以直接判断s[i]s[j]的最小值乘上j-i是不是大于5就可以了。

void dfs(int x){
	if(x>=ans)return ;
	memset(c,0,sizeof(c));
	for(int i=2;i<=14;i++)c[s[i]]++;//记录单牌,对子,三张,炸弹的数量
	ans=min(x+f[c[1]+s[0]][c[2]][c[3]][c[4]],ans);//这里是DP待会会讲
	//printf("%d %d %d %d %d %d\n",c[1]+s[0],c[2],c[3],c[4],x,ans);//调试
	if(s[0]==2)ans=min(ans,x+f[c[1]][c[2]][c[3]][c[4]]+1);//这里是DP待会会讲
	for(int i=3;i<=14;i++){
		for(int j=i+1;j<=14;j++){
			int a=1e9;
			for(int k=i;k<=j;k++)a=min(a,s[k]);
			if(a*(j-i+1)>=5){//这里用了一个小技巧
				for(int k=i;k<=j;k++)s[k]-=a;
				dfs(x+1);
				for(int k=i;k<=j;k++)s[k]+=a;
			}
		}
	}
}

2.其他牌种

首先我们可以发现其实其他的牌都是一样的,详细如下:

单牌零带一
对子零带二
三张三带零
三带一三带一
三带二三带二
炸弹四带零
四带二四带二

所以我们可以用DP解决剩余的牌种了

我们如果单牌,对子,三张,炸弹各有i,j,k,l个那么DP状态就好弄了:

如果出单牌:i-1;

如果出对子:j-1;

如果出三张:k-1;

如果出炸弹:l-1;

如果出三带一:i-1,k-1;

如果出三带二:j-1,k-1;

如果出四带两单:i-2,l-1;

如果出四带二双:j-2,l-1;

如果出四带一对:j-1,l-1;(注意!!!有这种情况)

你以为这就完啦?

呵呵。


3.拆牌

我们来看以下的手牌:

333,6666,JJJJ

如果你以为是三次,那么就大错特错,真正的答案是两次。

第一次:33366

第二次:JJJJ66

所以还没完呢!!!

如果对子拆成单牌:i+2,j-1;

如果三张拆成单牌和对子:i+1,j+1,k-1;

如果炸弹拆成单牌和三张:i+1,k+1,l-1;

如果炸弹拆成两个对子:j+2,l-1;

这里补充说明一下:如果三张拆成三个单牌的话,那么跟先拆成单牌和对子对子再拆成单牌是重复的。炸弹拆成四个单牌同理。

重点!!!DP的顺序一定是从L到I,因为L加的机会最少,其他的同理。

然后别忘记初始化!!!

恶心死的代码

void calc(){
	for(int l=0;l<=N;l++){
		for(int k=0;k<=N;k++){
			for(int j=0;j<=N;j++){
				for(int i=0;i<=N;i++)f[i][j][k][l]=1e9;//别忘记初始化!!!
			}
		}
	}
	f[0][0][0][0]=0;
	for(int l=0;l<=N;l++){
		for(int k=0;k<=N;k++){
			for(int j=0;j<=N;j++){
				for(int i=0;i<=N;i++){
					int cnt=1e9;
					//单出
					if(i>0)cnt=min(cnt,f[i-1][j][k][l]+1);//单张
					if(j>0)cnt=min(cnt,f[i][j-1][k][l]+1);//对子
					if(k>0)cnt=min(cnt,f[i][j][k-1][l]+1);//三张
					if(l>0)cnt=min(cnt,f[i][j][k][l-1]+1);//炸弹
					//带牌
					if(i>0&&k>0)cnt=min(cnt,f[i-1][j][k-1][l]+1);//三带一
					if(j>0&&k>0)cnt=min(cnt,f[i][j-1][k-1][l]+1);//三带二
					if(i>1&&l>0)cnt=min(cnt,f[i-2][j][k][l-1]+1);//四带二单
					if(j>0&&l>0)cnt=min(cnt,f[i][j-1][k][l-1]+1);//四带一对
					if(j>1&&l>0)cnt=min(cnt,f[i][j-2][k][l-1]+1);//四带两对
					//拆牌
					if(j>0)cnt=min(cnt,f[i+2][j-1][k][l]);//对子拆成两张单
					if(k>0)cnt=min(cnt,f[i+1][j+1][k-1][l]);//三张拆成一单一对
					if(l>0)cnt=min(cnt,f[i+1][j][k+1][l-1]);//炸弹拆成一个三张和一个单牌
					if(l>0)cnt=min(cnt,f[i][j+2][k][l-1]);//炸弹拆成两对
					f[i][j][k][l]=min(f[i][j][k][l],cnt);//记录答案
				}
			}
		}
	}
}

4.本题坑点

1.读入的时候一定要把A设成14因为A是在K后面。

2.DP顺序要弄好。

3.2和王不能带进顺子(应该不会错吧)。

4.王炸不能当做对子来看!!!

5.不要忘记初始化。

5.后记

这道题我整整调了1.5小时的bug,及其考验耐心和细心,还算不错的题目了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值