Week6 限时大模拟 A 掌握魔法の东东II

本文介绍了一个复杂的牌型概率计算问题,涉及从特定数量的扑克牌中抽取若干张牌后,剩余牌组成的各种牌型的可能性计算。文章详细描述了牌型的定义、输入输出格式、样例及算法分析,通过枚举和记忆化搜索策略,实现了对同花顺、炸弹等9种牌型的精确计算。

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

题目描述

从瑞神家打牌回来后,东东痛定思痛,决定苦练牌技,终成赌神!
东东有 A × B 张扑克牌。每张扑克牌有一个大小(整数,记为a,范围区间是 0 到 A - 1)和一个花色(整数,记为b,范围区间是 0 到 B - 1。
扑克牌是互异的,也就是独一无二的,也就是说没有两张牌大小和花色都相同。
“一手牌”的意思是你手里有5张不同的牌,这 5 张牌没有谁在前谁在后的顺序之分,它们可以形成一个牌型。 我们定义了 9 种牌型,如下是 9 种牌型的规则,我们用“低序号优先”来匹配牌型,即这“一手牌”从上到下满足的第一个牌型规则就是它的“牌型编号”(一个整数,属于1到9):

  1. 同花顺: 同时满足规则 5 和规则 4.
  2. 炸弹 : 5张牌其中有4张牌的大小相等.
  3. 三带二 : 5张牌其中有3张牌的大小相等,且另外2张牌的大小也相等.
  4. 同花 : 5张牌都是相同花色的.
  5. 顺子 : 5张牌的大小形如 x, x + 1, x + 2, x + 3, x + 4
  6. 三条: 5张牌其中有3张牌的大小相等.
  7. 两对: 5张牌其中有2张牌的大小相等,且另外3张牌中2张牌的大小相等.
  8. 一对: 5张牌其中有2张牌的大小相等.
  9. 要不起: 这手牌不满足上述的牌型中任意一个.

现在, 东东从A × B 张扑克牌中拿走了 2 张牌!分别是 (a1, b1) 和 (a2, b2). (其中a表示大小,b表示花色)
现在要从剩下的扑克牌中再随机拿出 3 张!组成一手牌!!
其实东东除了会打代码,他业余还是一个魔法师,现在他要预言他的未来的可能性,即他将拿到的“一手牌”的可能性,我们用一个“牌型编号(一个整数,属于1到9)”来表示这手牌的牌型,那么他的未来有 9 种可能,但每种可能的方案数不一样。
现在,东东的阿戈摩托之眼没了,你需要帮他算一算 9 种牌型中,每种牌型的方案数。

Input

第 1 行包含了整数 AB (5 ≤ A ≤ 25, 1 ≤ B ≤ 4).

第 2 行包含了整数 a1, b1, a2, b2 (0 ≤ a1, a2 ≤ A - 1, 0 ≤ b1, b2 ≤ B - 1, (a1, b1) ≠ (a2, b2)).

Output

输出一行,这行有 9 个整数,每个整数代表了 9 种牌型的方案数(按牌型编号从小到大的顺序)

Sample Input 1

5 2
1 0 3 1

Sample Output 1

0 0 0 0 8 0 12 36 0

Sample Input 2

25 4
0 0 24 3

Sample Output 2

0 2 18 0 0 644 1656 36432 113344

算法/思路分析

题目分析:本题牌最多有25*4张,已抽2张牌,再抽3张牌,即最多有C(98,3)= 152096种,因此可以直接模拟/暴力/枚举。

数据结构:卡片信息直接通过结构体数组c[A*B]记录,用结构体数组d[1]…d[5]记录每一种类型的5张牌,num[1],…,num[9]记录9种类型的数量。

判断类型:观察9个类型,除了类型四(同花)、类型九(要不起)外,均和牌大小相等的数量有关。因此可从此处入手,维护cnum[26]记录每一次组合大小为i的牌的数量。根据cnum[i] == x及其他辅助条件判断类型,具体见代码部分。

本题需要注意以下问题/细节:

(1)O(n3)枚举时,可通过vis[i] [j] [k]记录已经记录的组合,从而实现记忆化搜索防止重复检测同一种组合。(例如:(1,2,3)(1,3,2)(2,1,3)(2,3,1)(3,1,2)(3,2,1)是同一种组合

(2)每一个组合检测类型时,要优先初始化并重新记录cnum[0],…,cnum[A-1].

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
using namespace std;
const int maxn = 187;
int A,B;
int num[10] = {0};
struct card
{
	int a;
	int b;	
}c[maxn],d[6];

int vis[maxn][maxn][maxn] = {0}; //记忆化搜索
 
bool operator==(struct card c1,struct card c2)
{
	if(c1.a == c2.a && c1.b == c2.b) return true;
	else return false;
}

int cnum[26]; //记录大小为i的牌有多少个 

//同花
bool type4()
{
	if(d[1].b == d[2].b && d[2].b == d[3].b && d[3].b == d[4].b && d[4].b == d[5].b)
		return true;
	return false;
}
//顺子 
bool type5()
{
	for(int i = 0; i < A-4; i++)
		if(cnum[i] == 1)
		{
			if(cnum[i+1] == 1 && cnum[i+2] == 1 && cnum[i+3] == 1
			&& cnum[i+4] == 1)
				return true;
		}
	return false;
}
//同花顺 
bool type1()
{
	if(type4() && type5()) 
		return true;
	return false;
}
//炸弹 
bool type2()
{
	for(int i = 0; i < A;i++)
		if(cnum[i] == 4) return true;
	return false; 
}
//三带二 
bool type3()
{
	for(int i = 0; i < A;i++)
	{
		for(int j = 0; j < A; j++)
		{
			if(i == j) continue;
			if(cnum[i] == 3 && cnum[j] == 2) return true;
			else if(cnum[i] == 2 && cnum[j] == 3) return true;
		}
	}
	return false;
}
//三条 
bool type6()
{
	for(int i = 0; i < A;i++)
		if(cnum[i] == 3) return true;
	return false; 
}
//两对 
bool type7()
{
	for(int i = 0; i < A;i++)
	{
		if(cnum[i] == 2 && i < A-1)
		{
			for(int j = i + 1; j < A; j++) 
				if(cnum[j] == 2) return true;
		}
	}
	return false;
}
//一对 
bool type8()
{
	for(int i = 0; i < A;i++)
		if(cnum[i] == 2) return true;
	return false; 
}

//计数 
void count()
{
	//每次计数要重新统计cnum数组 
	for(int i = 0; i < A; i++) cnum[i] = 0;
	for(int i = 1; i <= 5; i++) cnum[d[i].a]++;
	//类型检测 
	if(type1()) num[1]++;
	else if(type2()) num[2]++;
	else if(type3()) num[3]++;
	else if(type4()) num[4]++;
	else if(type5()) num[5]++;
	else if(type6()) num[6]++;
	else if(type7()) num[7]++;
	else if(type8()) num[8]++;
	else num[9]++;
}

int main()
{
	scanf("%d%d",&A,&B);
	//初始化牌库 
	for(int i = 0; i < A; i++)
		for(int j = 0; j < B; j++)
		{
			c[i*B+j].a = i;
			c[i*B+j].b = j;
		}
	//拿两张牌 
	for(int i = 1; i <= 2;i++)	
		scanf("%d%d",&d[i].a,&d[i].b);
	int sum = A*B;
	
	for(int i = 0; i < sum; i++)
	{
		//不拿已经拿的牌 
		if(c[i] == d[1] || c[i] == d[2]) continue;
		d[3] = c[i];
		
		for(int j = 0; j < sum; j++)
		{
			//不拿已经拿的牌  
			if(c[j] == d[1] || c[j] == d[2] || c[j] == d[3]) continue;
			d[4] = c[j];
			
			for(int k = 0; k < sum; k++)
			{
				//不拿已经拿的牌 
				if(c[k] == d[1] || c[k] == d[2] || c[k] == d[3] || c[k] == d[4])
					continue;
				d[5] = c[k];
				//记忆化搜索:拿过的组合不需要再拿 
				if(vis[i][j][k] || vis[i][k][j] || vis[j][i][k]
				|| vis[j][k][i] || vis[k][i][j] || vis[k][j][i])
					continue;	
				vis[i][j][k] = 1;	
				//检测类型并计数
				count();
			}
		}
	} 
	
	//输出结果 
	for(int i = 1; i <= 9; i++)
	{
		if(i > 1) printf(" ");
		printf("%d",num[i]);
	}
	printf("\n");
	
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值