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;
}