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

posted @   Chen_Jinhui  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通

一言

你已经不是我的狗了。
——龙与虎
点击右上角即可分享
微信分享提示