A-一手牌的牌型可能性
题目描述:
从瑞神家打牌回来后,东东痛定思痛,决定苦练牌技,终成赌神!
东东有 A × B 张扑克牌。每张扑克牌有一个大小(整数,记为a,范围区间是 0 到 A - 1)和一个花色(整数,记为b,范围区间是 0 到 B - 1。
扑克牌是互异的,也就是独一无二的,也就是说没有两张牌大小和花色都相同。
“一手牌”的意思是你手里有5张不同的牌,这 5 张牌没有谁在前谁在后的顺序之分,它们可以形成一个牌型。 我们定义了 9 种牌型,如下是 9 种牌型的规则,我们用“低序号优先”来匹配牌型,即这“一手牌”从上到下满足的第一个牌型规则就是它的“牌型编号”(一个整数,属于1到9):
同花顺: 同时满足规则 2 和规则 3.
顺子 : 5张牌的大小形如 x, x + 1, x + 2, x + 3, x + 4
同花 : 5张牌都是相同花色的.
炸弹 : 5张牌其中有4张牌的大小相等.
三带二 : 5张牌其中有3张牌的大小相等,且另外2张牌的大小也相等.
两对: 5张牌其中有2张牌的大小相等,且另外3张牌中2张牌的大小相等.
三条: 5张牌其中有3张牌的大小相等.
一对: 5张牌其中有2张牌的大小相等.
要不起: 这手牌不满足上述的牌型中任意一个.
现在, 东东从A × B 张扑克牌中拿走了 2 张牌!分别是 (a1, b1) 和 (a2, b2). (其中a表示大小,b表示花色)
现在要从剩下的扑克牌中再随机拿出 3 张!组成一手牌!!
其实东东除了会打代码,他业余还是一个魔法师,现在他要预言他的未来的可能性,即他将拿到的“一手牌”的可能性,我们用一个“牌型编号(一个整数,属于1到9)”来表示这手牌的牌型,那么他的未来有 9 种可能,但每种可能的方案数不一样。
现在,东东的阿戈摩托之眼没了,你需要帮他算一算 9 种牌型中,每种牌型的方案数。
思路分析:
做这道题首先要理解清除题意,题目描述中加粗的字要好好揣摩,题干其他部分都很容易理解。
拿到这个题,首先会想到的是排列组合,但是排列组合是真的不适合解答这道题,因为排列组合考虑的情况下很多,而且很少脑,这根本没有好好利用代码的能力。言归正传,这道题到底该怎么做呢?
第一步:选出剩下的三张牌,这道题的时限2000ms,测试数据A最大为25,B最大为4,所以如果暴力穷举去选出剩下三张牌的所有可能性的复杂度是O((A*B)^3),这样的复杂度是完全可以接受的。把选出的牌的值和花色分别用数组val和ch存储下来(我这里是从索引1开始存储)。要注意的一点就是选牌的时候,要避免重复选牌,也就是牌的值和花色不能同时与已经被选出的某张牌相同。
第二步:判断牌型。我只需要在选出的这五张牌中进行判断牌型就好。这里有个小技巧,就是把val和ch分别进行从小到大的排序。
(1)同花顺:(2)和(3)同时满足。
(2)顺子:只关注牌的值不管花色,也就是只看已经排好序的val数组,只要这个数组满足val[i]-val[i-1] =1(2<=i<=5)就说明这个数组中5张牌的值是连续的。
(3)同花:只看花色不看值,只关注ch数组,同花要求五张牌花色一致,也就是ch中五个值相等,由于ch已经排好序,所以如果ch[1]=ch[5]成立就说明5张牌同花色了。
(4)炸弹:只看牌值不看花色,只看排好序的val,炸弹要求四张牌大小相等,如果是炸弹,那么在排好序的val中一定有连着四个数字相等,要么是val[1].val[2].val[3].val[4],要么是val[2],val[3],val[4],val[5]。
(6)三带二:只看val数组值,val数组中一定又连续两个数字相等,另外连续三个数字相等,要么就是val[1]=val[2]=val[3]&&val[4]=val[5],要么就是val[1]=val[2]&&val[3]=val[4]=val[5]。
(7)~(8)的分析也类似,就不细说了。
注意!!!:最后的结果一定要除以6,因为穷举选择三张牌的时候,我只考虑到了不能选重复牌,但是!!!有可能不同循环下选中了已经在之前的循环下被选中的三张牌了,只是三张牌被选的顺序不同而已。
代码实现:
#include<cstdio>
#include<algorithm>
using namespace std;
int a,b,a1,b1,a2,b2;
int val[6],ch[6],res[10]={0};//5张牌 ,9种可能性
//先暴力枚举出一手牌的所有组合,分别从小到大存储下值和花色
bool shunzi_2()
{
for(int i=2;i<=5;i++)
if(val[i]-val[i-1]!=1)
return false;
return true;
}
bool tonghua_3()
{
if(ch[1]==ch[5])
return true;
return false;
}
bool tonghuashun_1()
{
if(shunzi_2()&&tonghua_3())
return true;
return false;
}
bool zhadan_4()
{
if(val[1]==val[4]||val[2]==val[5])
return true;
return false;
}
bool sandaier_5()
{
if(val[1]==val[3]&&val[4]==val[5])
return true;
if(val[1]==val[2]&&val[3]==val[5])
return true;
return false;
}
bool liangdui_6()
{
if(val[1]==val[2]&&val[3]==val[4])
return true;
if(val[1]==val[2]&&val[4]==val[5])
return true;
if(val[2]==val[3]&&val[4]==val[5])
return true;
return false;
}
bool santiao_7()
{
if(val[1]==val[3]||val[2]==val[4]||val[3]==val[5])
return true;
return false;
}
bool yidui_8()
{
if(val[1]==val[2]||val[2]==val[3]||val[3]==val[4]||val[4]==val[5])
return true;
return false;
}
int main()
{
scanf("%d%d",&a,&b);
scanf("%d%d%d%d",&a1,&b1,&a2,&b2);//接下来选出三张牌
for(int i3=0;i3<a;i3++)
for(int j3=0;j3<b;j3++)
if((i3!=a1||j3!=b1)&&(i3!=a2||j3!=b2))
for(int i4=0;i4<a;i4++)
for(int j4=0;j4<b;j4++)
if((i4!=a1||j4!=b1)&&(i4!=a2||j4!=b2)&&(i4!=i3||j4!=j3))
for(int i5=0;i5<a;i5++)
for(int j5=0;j5<b;j5++)
if((i5!=a1||j5!=b1)&&(i5!=a2||j5!=b2)&&(i5!=i3||j5!=j3)&&(i5!=i4||j5!=j4))
{
val[1]=a1;val[2]=a2;val[3]=i3;val[4]=i4;val[5]=i5;//数组的赋值必须在循环内做!!!
ch[1]=b1;ch[2]=b2;ch[3]=j3;ch[4]=j4;ch[5]=j5;
sort(val+1,val+6);
sort(ch+1,ch+6);
if(tonghuashun_1()) res[1]++;
else if(shunzi_2()) res[2]++;
else if(tonghua_3()) res[3]++;
else if(zhadan_4()) res[4]++;
else if(sandaier_5()) res[5]++;
else if(liangdui_6()) res[6]++;
else if(santiao_7()) res[7]++;
else if(yidui_8()) res[8]++;
else res[9]++;
}
for(int i=1;i<=9;i++)
printf("%d ",res[i]/6);
}
总结分析:
做这道题我一开始也是排列组合,把自己绕来绕去,非常烧脑,所以下意识想到的办法真的真的很可能不是最优的!!!而且一般来说,我都是有意识的去避免暴力穷举。所以以后做题一定要去看看测试数据范围,看看时间限制,也多多关注一下给的空间限制,这对于我们的解题有很大帮助!!!