PTA-德州扑克 题解
于2020/02/24记录。
德州扑克属实是个带难题。本题解简单易懂,命名合理,应该比较好理解。
题目如下:
最近,阿夸迷于德州扑克。所以她找到了很多人和她一起玩。由于人数众多,阿夸必须更改游戏规则:
所有扑克牌均只看数字,不计花色。
每张卡的值为1、2、3、4、5、6、7、8、9、10、11、12、13 中的一种(对应A,2、3、4、5、6、7, 8、9、10,J,Q,K)
每位玩家从一副完整的扑克牌(没有大小王)中抽出五张扑克牌,可能出现的手牌的值从低到高排列如下:
高牌:不包含以下牌的牌。对于都是高牌的牌,按照五张牌的值的和进行从大到小排序。
对子:手中的5张牌中有2张相同值的牌。对于都拥有对子的牌,按构成该对子的牌的值进行从大到小地排序。如果这些都相同,则按手牌中余下3张牌的值的和进行从大到小排序。
两对:手中拥有两对不同的对子。对于都包含两对的手牌,按其最高对子的值进行从大到小排序。如果最高对子相同,则按另一个对子的值从大到小地进行排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
三条:手中拥有3张相同值的牌。对于都包含三条的手牌按构成三条的牌的值进行从大到小地排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
满堂红:手中拥有一个三条和一个对子。同理,先按三条大小排序,如果三条大小相同,则按对子大小进行排序。
四条:手中拥有4张相同值的牌。对于都包含四条的手牌按构成四条的牌的值进行从大到小地排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
顺子:手中拥有5张连续值的卡。对于都包含顺子的手牌按顺子最大的牌进行排序。
皇家同花顺:手中拥有10到A(10、J、Q、K、A)。是最大的手牌!
现在,阿夸已经知道了每个人的手牌,她想要知道所有人的排名列表。如果玩家的手牌大小相等,则按玩家名字的字典序输出。保证没有重复的名字。你能帮帮她吗?
输入格式:
第一行包含一个正整数 N (1<=N<=100000) ,表示玩家的人数。
接下来 N 行,每行包含两个字符串:m (1<=|m|<=10 ) ,表示玩家的名字;s (1<=|s|<=10),表示玩家的手牌。
输出格式:
输出 N个玩家的排名列表。
输入样例:
3
Alice AAA109
Bob 678910
Boa 678910
输出样例:
Boa
Bob
Alice
解:
头文件
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
本题包含一个结构体struct People、三个函数:①int trans(string str)②void deal_the_level_of(People x,int cur)③int cmp(People a,People b)、主函数:
struct People
{
string name;
string s;
int card[14];
double level;//大等级从1-8 细分等级顺序用小数点来算大小
}f[100005];
结构体的成员很简单:名字和手牌都为string类,方便输入输出和拆分;card数组分别储存这个人拥有的牌的情况;最后一个最重要的等级,我用了double型来定义,通过小数点来判定牌面大小的优先级;同时定义一个全局变量的数组方便后续输入和处理。
int trans(string str)
{
if(str=="A")return 1;
if(str=="2")return 2;
if(str=="3")return 3;
if(str=="4")return 4;
if(str=="5")return 5;
if(str=="6")return 6;
if(str=="7")return 7;
if(str=="8")return 8;
if(str=="9")return 9;
if(str=="10")return 10;
if(str=="J")return 11;
if(str=="Q")return 12;
if(str=="K")return 13;
}
trans函数的功能想必大家都不陌生,之前的白话文编程作业多次需要将字符串转化为有数值的数字,这个函数也是一个将string类的字符串转化为数字,即对应的牌面。
int cmp(People a,People b)
{
if(a.level!=b.level)return a.level>b.level;
else return a.name<b.name;
}
这个cmp(compare)函数是给后面要用的sort函数作为其第三个参数,说白了就是告诉sort怎么进行排序。本题是按照牌面从大到小来排,如果牌面相同就按照名字的字典序来排。由于本题用的是string类,进行字典序排序十分简单,一个小于号“<”就搞定。
下面是本题最重要的函数!!由于这个函数很长,有70多行,所以将解释放入注释中。
函数顾名思义--deal the level of (某个People型的变量):处理People型变量x的等级。
void deal_the_level_of(People x,int cur) //current 当前的那个下标
{
int single_value=0;//剩下单张牌总值的和
int couple1=0,couple2=0;//有一个对子 、有两个对子 其值为对子对应的牌面
int three=0; //判断是否有三条
int four=0; //判断是否有四条
bool line=false;//判断是否有顺子 有的话更为true
int maxn;//顺子中最大的牌
for(int i=1;i<=13;i++)
{
if(x.card[i]==1)//遇到这张牌是单张的
{
int judge=1,j;//判断顺子
for(j=i+1;j<=i+4;j++)judge*=x.card[j];//不断相乘,只有全都是 1 ,乘出来才会为1.
if(judge==1){//是顺子
line=true;
maxn=j-1;
break;
}
else single_value+=i;
}
if(x.card[i]==2)//如果这张牌拥有两张
{
if(couple1==0)couple1=i;//如果之前没出现过对子
else couple2=i;//之前出现过对子了,那这个就是第二个对子
}
if(x.card[i]==3)three=i;//如果这张牌拥有三张
if(x.card[i]>=4)//这张牌拥有四张或五张,一样的都归为 四条 这个级别。
{
four=i;
if(x.card[i]==5)single_value+=i;//五张一样的 剩下那张归为单张
}
}
/*
注意!如果一个大等级中,有多个排序的优先级
那么:
第一优先级 乘 0.01
第二优先级 0.0001
第三优先级 0.000001
因为最大的单张牌面“K”的值为13,超过10了,如果少乘一个0会影响到大等级。
*/
if(x.card[10]*x.card[11]*x.card[12]*x.card[13]*x.card[1]==1)x.level+=8.0;//皇家同花顺 ,永远滴神!
else if(line)//顺子 第7级
{
x.level=7.0;
x.level+=maxn*0.01;
}
else if(four)//四条 第6级
{
x.level=6.0;
x.level+=four*0.01+single_value*0.0001;
}
else if(three)//满堂红或三条
{
if(couple1){//有一个对子,满堂红 第5级
x.level=5.0;
x.level+=three*0.01+couple1*0.0001;
}
else{//三条 第4级
x.level=4.0;
x.level+=three*0.01+single_value*0.0001;
}
}
else if(couple1)//两对或对子
{
if(couple2){//两对 第3级
x.level=3.0;
x.level+=couple2*0.01+couple1*0.0001+single_value*0.000001;
}
else{//单个对子 第2级
x.level=2.0;
x.level+=couple1*0.01+single_value*0.0001;
}
}
else {//高牌 最弱的第1级
x.level=1.0;
x.level+=single_value*0.0001;
}
f[cur]=x;//不用指针,将其转移给当前的这个f (因为f数组是全局变量) ,不然f数组的值不会改变的
}
主函数:
解释还是放在注释里啦!
int main()
{
int n,value;
string tmp;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>f[i].name>>f[i].s;
string str=f[i].s;
for(int j=0,t=0;t<5;t++)//共五张牌
{
if(str[j]!='1'){ //不为10
tmp=str[j]; //提取出这个字符
value=trans(tmp);//转换这个字符为对应的值
f[i].card[value]++;//该值的手牌数+1
j++;
}
else{ //遇到10了,只有 10 才会出现 1这个数字,因为牌值为1的是 A
f[i].card[10]++;
j+=2; //跳两格
}
}
deal_the_level_of(f[i],i);//处理f[i],记得带下标i
}
sort(f,f+n,cmp); //排序
for(int i=0;i<n;i++)cout<<f[i].name<<endl; //输出
return 0;
}
我遇到的问题:
细节实在太多了,大量的分类情况堆在一起会十分混乱,所以在编写时最好用笔在一张草稿纸写出分类、优先级排序的思路。
一共有10个测试点,我的过程为:
通过0个点->通过第1、2个点:修正了“两对”这个情况的bug。
错误代码为
if(x.card[i]==2)//如果这张牌拥有两张
{
if(couple1==0)couple1=i;//如果之前没出现过对子
if(couple1)couple2=i;//之前出现过对子了,那这个就是第二个对子
}
写了两个if语句,问题在于两条语句的顺序错了。执行完第一句后,couple1已经被赋值了,第二句他必不可能为0,所以第二句也会被执行,从而couple2会变成和couple1一样的值。
正确代码应为
if(x.card[i]==2)//如果这张牌拥有两张
{
if(couple1==0)couple1=i;//如果之前没出现过对子
else couple2=i;//之前出现过对子了,那这个就是第二个对子
}
或者将两句顺序调换。
通过2个点->AC:修正了满堂红和三条的bug,我原先没判断三子的大小直接判断了后面剩余牌的大小,此块优先级应该为:先比较三子大小、后比较剩余两张牌的大小。
错误代码为
else if(three)//满堂红或三条
{
if(couple1){//有一个对子,满堂红 第5级
x.level=5.0;
x.level+=couple1*0.01;
}
else{//三条 第4级
x.level=4.0;
x.level+=single_value*0.01;
}
}
正确代码应为
else if(three)//满堂红或三条
{
if(couple1){//有一个对子,满堂红 第5级
x.level=5.0;
x.level+=three*0.01+couple1*0.0001;
}
else{//三条 第4级
x.level=4.0;
x.level+=three*0.01+single_value*0.0001;
}
}
希望能对大家有所帮助:)
完整代码:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
struct People
{
string name;
string s;
int card[14];
double level;//大等级从1-8 细分等级顺序用小数点来算大小
}f[100005];
int trans(string str)
{
if(str=="A")return 1;
if(str=="2")return 2;
if(str=="3")return 3;
if(str=="4")return 4;
if(str=="5")return 5;
if(str=="6")return 6;
if(str=="7")return 7;
if(str=="8")return 8;
if(str=="9")return 9;
if(str=="10")return 10;
if(str=="J")return 11;
if(str=="Q")return 12;
if(str=="K")return 13;
}
int cmp(People a,People b)
{
if(a.level!=b.level)return a.level>b.level;
else return a.name<b.name;
}
void deal_the_level_of(People x,int cur)//current 当前的那个下标
{
int single_value=0;//剩下单张牌总值的和
int couple1=0,couple2=0;//有一个对子 、有两个对子 其值为对子对应的牌面
int three=0; //判断是否有三条
int four=0; //判断是否有四条
bool line=false;//判断是否有顺子 有的话更为true
int maxn;//顺子中最大的牌
for(int i=1;i<=13;i++)
{
if(x.card[i]==1)//遇到这张牌是单张的
{
int judge=1,j;//判断顺子
for(j=i+1;j<=i+4;j++)judge*=x.card[j];//不断相乘,只有全都是 1 ,乘出来才会为1.
if(judge==1){//是顺子
line=true;
maxn=j-1;
break;
}
else single_value+=i;
}
if(x.card[i]==2)//如果这张牌拥有两张
{
if(couple1==0)couple1=i;//如果之前没出现过对子
else couple2=i;//之前出现过对子了,那这个就是第二个对子
}
if(x.card[i]==3)three=i;//如果这张牌拥有三张
if(x.card[i]>=4)//这张牌拥有四张或五张,一样的都归为 四条 这个级别。
{
four=i;
if(x.card[i]==5)single_value+=i;//五张一样的 剩下那张归为单张
}
}
/*
注意!如果一个大等级中,有多个排序的优先级
那么:
第一优先级 乘 0.01
第二优先级 0.0001
第三优先级 0.000001
因为最大的单张牌面“K”的值为13,超过10了,如果少乘一个0会影响到大等级。
*/
if(x.card[10]*x.card[11]*x.card[12]*x.card[13]*x.card[1]==1)x.level+=8.0;//皇家同花顺 ,永远滴神!
else if(line)//顺子 第7级
{
x.level=7.0;
x.level+=maxn*0.01;
}
else if(four)//四条 第6级
{
x.level=6.0;
x.level+=four*0.01+single_value*0.0001;
}
else if(three)//满堂红或三条
{
if(couple1){//有一个对子,满堂红 第5级
x.level=5.0;
x.level+=three*0.01+couple1*0.0001;
}
else{//三条 第4级
x.level=4.0;
x.level+=three*0.01+single_value*0.0001;
}
}
else if(couple1)//两对或对子
{
if(couple2){//两对 第3级
x.level=3.0;
x.level+=couple2*0.01+couple1*0.0001+single_value*0.000001;
}
else{//单个对子 第2级
x.level=2.0;
x.level+=couple1*0.01+single_value*0.0001;
}
}
else {//高牌 最弱的第1级
x.level=1.0;
x.level+=single_value*0.0001;
}
f[cur]=x;//不用指针,将其转移给当前的这个f (因为f数组是全局变量) ,不然f数组的值不会改变的
}
int main()
{
int n,value;
string tmp;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>f[i].name>>f[i].s;
string str=f[i].s;
for(int j=0,t=0;t<5;t++)//共五张牌
{
if(str[j]!='1'){ //不为10
tmp=str[j]; //提取出这个字符
value=trans(tmp);//转换这个字符为对应的值
f[i].card[value]++;//该值的手牌数+1
j++;
}
else{ //遇到10了,只有 10 才会出现 1这个数字,因为牌值为1的是 A
f[i].card[10]++;
j+=2; //跳两格
}
}
deal_the_level_of(f[i],i);//处理f[i],记得带下标i
}
sort(f,f+n,cmp); //排序
for(int i=0;i<n;i++)cout<<f[i].name<<endl; //输出
return 0;
}
好难TOT