P5013 水の斗牛 题解
注意,出题人脚造的第二个点中存在用 1 代表 Ace 牌的情况。
大模拟常规操作:建立结构体/类。
这里我建了两个,分别是卡牌和玩家两个结构体。
struct card{//创建card结构体
char h;//花色
int d;//点数
};
struct player{//创建player结构体
string name;//名字
card c[5];//存储玩家拥有的牌
int px;//牌型
int pt;//玩家的分数
void pdpx();//判断牌型函数
void cardempty(){
for(int i=0;i<5;i++) c[i].h=' ',c[i].d=0;
px=0;
return;
};//清空玩家手牌
}pl[100001];
其中 pdpx 函数是本题要实现的重点之一,下一部分单独讲。
pdpx 函数具体实现方法如下:
首先先把牌排序一下,这样方便找出相同的牌,也方便在之后判断大小的过程中快速判断。
牌大小以
点数更大的一方更大;若双方点数最大的一张牌点数相同,则花色更大的一方更大,花色大小为黑桃>红桃>梅花>方块。
为准。
之后暴力枚举三张牌组成10的倍数,算出牌型,特判一下铁板和炸弹。
注意:若铁板牌型比原来牌型要差,应选择原来牌型。
bool cmp(card x, card y){
return x.d>y.d||(x.d==y.d&&x.h<y.h);
//点数从大到小排序,如点数一样,花色从小到大排序。
//因为黑桃(a)>红桃(b)>梅花(c)>方块(d),所以从小到大
}
void player::pdpx(){
sort(c,c+5,cmp);
int sum=0;
for(int i=0;i<5;i++) sum+=c[i].d;//记录总和
bool flag=false;
for(int i=0;i<5&&!flag;i++)
for(int j=i+1;j<5&&!flag;j++)
for(int k=j+1;k<5&&!flag;k++){//枚举三张牌组成整十数
if((c[i].d+c[j].d+c[k].d)%10==0){
flag=true;
px=(sum-1)%10+1;//算牛几
}
}
if(!flag) px=0;
int xt=1,st=0;//记录相同的数的个数 及 连续相同的数起始点
for(int i=1;i<5;i++){
if(c[i].d==c[i-1].d) xt++;
else {
if(xt<3) xt=1,st=i;
else break;
}
}
if(xt==3){//说明存在铁板
if((sum-c[st].d*3-1)%10+1>=px){//若用铁板的牌型优于原牌型
for(int i=0;i<=2;i++) swap(c[i],c[i+st]);//将铁板移至最前面
px=100+(c[3].d+c[4].d-1)%10+1;
}
}
else if(xt==4){//说明是炸弹
px=11;
if(c[0].d!=c[1].d)//说明炸弹不在前四个而在后四个
for(int i=1;i<5;i++) swap(c[i],c[i-1]);//将单张移到最后
}
return;
}
然后是计分函数。
感觉解释起来有点太过于冗长,配合如下代码食用。
const int df=10;//底分
int a[12]={1,1,1,1,1,1,1,2,2,2,3,10};
//a数组存储每一种牌型的分数是底分的多少倍。
//0指无牛,1~9指牛一~牛九,10指牛牛,11指炸弹,100+x指铁板牛x。
void xwin(player &x,player &y){
int s;
s=a[x.px%100]/*除了铁板的分值*/*df*((x.px>100)+1)/*如是铁板乘2,否则乘1*/;
x.pt+=s;
y.pt-=s;
return;
}
void ywin(player &x,player &y){//同上
int s;
s=a[y.px%100]*df*((y.px>100)+1);
x.pt-=s;
y.pt+=s;
return;
}
void jf(player &x, player &y){
if(x.px%100>y.px%100) xwin(x,y);//因为有铁板的关系,所以要%100
else if(y.px%100>x.px%100) ywin(x,y);
else if(y.px%100==x.px%100){//当牌型(去铁板)相同时
if(y.px>100&&x.px<100) ywin(x,y);
else if(y.px<100&&x.px>100) xwin(x,y);
else if(y.px==x.px){//当双方都有/没有铁板时
if(x.c[0].d>y.c[0].d) xwin(x,y);
else if(y.c[0].d>x.c[0].d) ywin(x,y);
else if(y.c[0].d==x.c[0].d){//当第一张牌点数相同时
if(x.c[0].h<y.c[0].h) xwin(x,y);
else ywin(x,y);
}
}
}
//cout<<x.px<<" "<<y.px<<" "<<x.pt<<" "<<y.pt<<endl;
return;
}
最后就是输入输出处理一下。
map<string,int> P;
void work(){
int id;
cin>>id>>T>>N;
for(int i=1;i<=N;i++) {
cin>>pl[i].name;P[pl[i].name]=i;//用map记录,快速找到名字对应编号
}
for(int k=1;k<=T;k++){
string nm;//名字
string s;//牌
int ds,p[3];//点数 存储玩家编号
for(int i=0;i<=2;i++){
cin>>nm;
p[i]=P[nm];
pl[p[i]].cardempty();//清空
for(int j=0;j<5;j++){
cin>>s;
pl[p[i]].c[j].h=s[0];
if(s.length()==3) pl[p[i]].c[j].d=10;//形如a10,长度为3
else if(s[1]=='A') pl[p[i]].c[j].d=1;
else pl[p[i]].c[j].d=s[1]-'0';
}
pl[p[i]].pdpx();
}
jf(pl[p[0]],pl[p[1]]);
jf(pl[p[0]],pl[p[2]]);
jf(pl[p[1]],pl[p[2]]);//三人轮流
}
for(int i=1;i<=N;i++) cout<<pl[i].name<<" "<<pl[i].pt<<'\n';
return;
}