题解 P5013 水の斗牛

· · 题解

题解 P5013 水の斗牛

校内的考试考到了这题,但是因为组题人数据错误没能过,在正确数据下过了,记录一下。

题意

模拟题,很简单,没什么好说的,按照题目来就行。

分析

梳理一下模拟题的流程:

  1. 确定牌型。
  2. 比较大小。
  3. 胜负计分。

我们按照上面的三步流程来分析一下这道题目。

1. 确定牌型

(1)炸弹

直接在 5 张牌中枚举 4 张来判断点数是否相等,找到一组就立即存下来跳出循环。

(2)牛牛

见下面的(3)牛,根据给出的大小关系可以直接将牛牛理解为牛十。

(3)牛

直接在 5 张牌枚举 3 张牌判断点数是否相等(铁板)或相加为 10 的倍数,为了让分数最大需要取最大值。

(4)无牛

枚举找不到满足的牛,记为牛零。

2. 比较大小

题目给出的关系中,牛牛大于牛九,牛一大于无牛,这就是为什么前文要将牛牛记为牛十,无牛记为牛零。

比较大小时先比较炸弹,再比较牛数,其次比较铁板,最后比较最大牌。

3. 胜负计分

直接根据题意,两两比较大小后判断胜负,再按照题目给出的规则更新得分即可。

模拟题具体实现就看代码吧,毕竟思路就是按照题面的顺序来。

代码

//the code is from chenjh
#include<iostream>
#include<unordered_map>
#include<string>
#include<utility>
#include<vector>
#define MAXN 100005
using namespace std;
const int pn=5;//每个人的手牌数量。
int id,T,n;
string nm[MAXN];//每位玩家的名字。
unordered_map<string,int> ud;//名字对应的玩家编号。
struct PLAYER{
    pair<char,int> p[pn];//牌(花色,点数) 
    int mp;//最大的一张牌编号。
    vector<int> sp;//特殊牌型:炸弹、牛牌的编号。 
    int ss,ssp;//牌点数之和,特殊牌型点数之和。
    int niu(const int x)const{return x%10?x%10:10;}//获取牛数,如果为 0 即为牛牛。
    void init(){
        sp.clear();
        mp=ss=ssp=0;
        for(int i=0;i<pn;i++) ss+=p[i].second;//手牌点数之和。
        for(int i=1;i<pn;i++)
            if(p[i].second>p[mp].second||(p[i].second==p[mp].second&&p[i].first<p[mp].first)) mp=i;//获取最大牌。
        for(int i=0;i<pn;i++)for(int j=i+1;j<pn;j++)if(p[i].second==p[j].second)
            for(int k=j+1;k<pn;k++)if(p[i].second==p[k].second)
                for(int l=k+1;l<pn;l++)if(p[i].second==p[l].second){sp={i,j,k,l};break;}//枚举寻找炸弹。
        if(sp.empty())//如果没有找到炸弹就寻找牛。
            for(int i=0;i<pn;i++)for(int j=i+1;j<pn;j++)for(int k=j+1;k<pn;k++)
                if((!((p[i].second+p[j].second+p[k].second)%10)&&(sp.empty()||niu(ss-(p[i].second+p[j].second+p[k].second))>niu(ss-ssp)))||//牌的点数和为 10 的倍数。
                ((p[i].second==p[j].second&&p[i].second==p[k].second)&&(sp.empty()||niu(ss-(p[i].second+p[j].second+p[k].second))>=niu(ss-ssp)))){//三张相同牌且点数更优。(能够取等的原因是铁板有着更为优秀的性质)
                    sp={i,j,k},ssp=0;
                    for(const int x:sp) ssp+=p[x].second;
                }
        ssp=0;for(const int x:sp) ssp+=p[x].second;
    }
    int BM()const{return sp.size()==4?p[sp[0]].second:0;}//判断炸弹。 
    int FE()const{return sp.size()==3&&p[sp[0]].second==p[sp[1]].second&&p[sp[1]].second==p[sp[2]].second?p[sp[0]].second:0;}//判断铁板,即三张牌点数相等。 
    int CW()const{return sp.size()==3?niu(ss-ssp):0;}//牛的分数。
    bool operator < (const PLAYER&B)const{//注意这里是重载的小于运算符。
        if(B.BM()!=BM()) return BM()<B.BM();//炸弹点数更小。
        else{
            if(CW()!=B.CW()) return CW()<B.CW();//牛数不等判断牛数大小。
            else if(CW()&&B.CW()&&FE()!=B.FE()) return FE()<B.FE();//有牛判断铁板。
            else return p[mp].second<B.p[B.mp].second||(p[mp].second==B.p[B.mp].second&&p[mp].first>B.p[B.mp].first);//都没有比较最大的牌。
        }
        return 0;
    }
}a[MAXN];
int b[MAXN];
void solve(){
    vector<int> pl(3);//当前对局的三个人。
    for(int i=0,x;i<3;i++){
        string nnm;cin>>nnm;
        x=ud[nnm];//获取到选手的编号。
        for(int j=0;j<pn;j++){
            cin>>nnm;//获取当前手牌。
            a[x].p[j]=nnm.size()==3?make_pair(nnm[0],10):make_pair(nnm[0],nnm[1]=='A'?1:nnm[1]-'0');//牌长度为 3 即点数为 10,第 2 个字符为 A 即点数为 1.
        }
        a[x].init();//初始化玩家。
        pl[i]=x;//记录对局选手编号。
    }
    for(int x=0;x<3;x++)for(int y=x+1;y<3;y++){//两两比较,计算分数得失。
        int i=pl[x],j=pl[y],d=1;
        if(a[i]<a[j]) swap(i,j);//交换后满足条件玩家 i 胜 j 负。
        if(a[i].BM()) d=10;//有炸弹底分倍数为 10。
        else{
            if(a[i].CW()==10) d=3;//牛牛倍数为 3。
            else if(7<=a[i].CW()&&a[i].CW()<=9) d=2;//牛七/牛八/牛九倍数为 2。
            else if(a[i].CW()<=6) d=1;//牛六~牛一/无牛倍数为 1.
            if(a[i].FE()) d<<=1;//单独计算铁板的翻倍。
        }
        b[i]+=d,b[j]-=d;//得失分数。
    }
}
int main(){
    ios::sync_with_stdio(false),cin.tie(nullptr);
    cin>>id>>T>>n;
    for(int i=1;i<=n;i++) cin>>nm[i],ud[nm[i]]=i;
    while(T--) solve();
    for(int i=1;i<=n;i++) cout<<nm[i]<<' '<<10*b[i]<<'\n';//乘上底分 10.
    return 0;
}