【noip 2015】 斗地主(搜索、记忆化搜索)

本文详细解析了斗地主游戏中如何通过算法优化出牌策略,以实现最少次数出完所有牌的目标。文章首先介绍了基本的出牌规则,随后提出了通过记忆化搜索结合状态压缩的方法来解决复杂带牌问题,避免了直接贪心策略可能遇到的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

题意

斗地主。

问你最少几次能把所有牌出完。

具体出牌规则详见各大 OJ 。

思路

很明显,先把所有顺子暴力搜掉,然后处理带的问题。

我原本想直接贪心做带的问题,但是非常麻烦。考虑到牌数非常少,可以直接记录一个状态 f c 1 , c 2 , c 3 , c 4 , c 0 f_{c1,c2,c3,c4,c0} fc1,c2,c3,c4,c0 分别表示有 1,2,3,4 张牌的牌种的数量和王的数量。

然后记忆化搜索就好了。注意一种有多张的牌可能会被拆开成多张散牌被别人带,这是比较坑的点。然后注意不要忘记王。

考察细心。

代码

#include<bits/stdc++.h>
using namespace std;
int T, n, cnt[15], tmp[5], f[24][13][8][6][3], ans;

void chkMin(int &x, int y){if (y < x) x = y;}

int dp(int c1, int c2, int c3, int c4, int c0){
	if (f[c1][c2][c3][c4][c0] != -1) return f[c1][c2][c3][c4][c0];
	int ret = n;
	if (!c1 && !c2 && !c3 && !c4 && !c0) ret = 0;
	if (c1) chkMin(ret, dp(c1-1, c2, c3, c4, c0)+1);
	if (c2){
		chkMin(ret, dp(c1+2, c2-1, c3, c4, c0));
		chkMin(ret, dp(c1, c2-1, c3, c4, c0)+1);
	}
	if (c3){
		chkMin(ret, dp(c1+3, c2, c3-1, c4, c0));
		chkMin(ret, dp(c1+1, c2+1, c3-1, c4, c0));
		if (c2) chkMin(ret, dp(c1, c2-1, c3-1, c4, c0)+1);
		if (c1) chkMin(ret, dp(c1-1, c2, c3-1, c4, c0)+1);
		if (c0) chkMin(ret, dp(c1, c2, c3-1, c4, c0-1)+1);
		chkMin(ret, dp(c1, c2, c3-1, c4, c0)+1);
	}
	if (c4){
		chkMin(ret, dp(c1+1, c2, c3+1, c4-1, c0));
		chkMin(ret, dp(c1, c2+2, c3, c4-1, c0));
		chkMin(ret, dp(c1, c2, c3, c4-1, c0)+1);
		if (c2 >= 2) chkMin(ret, dp(c1, c2-2, c3, c4-1, c0)+1);
		if (c1 >= 2) chkMin(ret, dp(c1-2, c2, c3, c4-1, c0)+1);
		if (c0 >= 2) chkMin(ret, dp(c1, c2, c3, c4-1, c0-2)+1);
		if (c1 && c0) chkMin(ret, dp(c1-1, c2, c3, c4-1, c0-1)+1);
	}
	if (c0) chkMin(ret, dp(c1, c2, c3, c4, 0)+1);
	return f[c1][c2][c3][c4][c0] = ret;
}

void dfs(int now, int stp){
	if (stp >= ans) return;
	if (now == 4){
		memset(tmp, 0, sizeof tmp);
		for (int i = 2; i <= 14; ++ i)
			tmp[cnt[i]]++;
		tmp[0] = cnt[0];
		stp += dp(tmp[1], tmp[2], tmp[3], tmp[4], tmp[0]);
		chkMin(ans, stp);
		return;
	}
	else if (now == 1){ // 三顺
		for (int i = 3; i <= 13; ++ i){
			for (int j = i; j <= 15; ++ j){
				if (cnt[j] < 3 || j == 15){
					for (int k = i; k < j; ++ k) cnt[k] += 3;
					break;
				}
				cnt[j] -= 3;
				if (j-i+1 >= 2) dfs(now, stp+1);
			}
		}
	}
	else if (now == 2){ // 双顺
		for (int i = 3; i <= 12; ++ i){
			for (int j = i; j <= 15; ++ j){
				if (cnt[j] < 2 || j == 15){
					for (int k = i; k < j; ++ k) cnt[k] += 2;
					break; 
				}
				cnt[j] -= 2;
				if (j-i+1 >= 3) dfs(now, stp+1);
			}
		}
	}
	else if (now == 3){ // 单顺
		for (int i = 3; i <= 10; ++ i){
			for (int j = i; j <= 15; ++ j){
				if (cnt[j] < 1 || j == 15){
					for (int k = i; k < j; ++ k) cnt[k]++;
					break; 
				}
				cnt[j]--;
				if (j-i+1 >= 5) dfs(now, stp+1);
			}
		}
	}
	dfs(now+1, stp);
}

int main()
{
	memset(f, -1, sizeof f);
	for (scanf("%d%d", &T, &n); T--; ){
		memset(cnt, 0, sizeof cnt);
		ans = n;
		for (int i = 1; i <= n; ++ i){
			int x, y;
			scanf("%d%d", &x, &y);
			if (x == 1) x = 14;
			cnt[x]++;
		}
		dfs(1, 0);
		printf("%d\n", ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值