【问题描述】
斯诺克台球是一项古老而又时尚的运动,使用长方形球桌,台面四角以及两长边中心位置各有一个球袋,使用的球分为1个白球,15个红球和6个彩球共22个球。
其中母球(白球)1只,目标球21只。目标球中:红球15只各1分、黄球1只2分、绿球1只3分、咖啡球1只4分、蓝球1只5分、粉球1只6分、黑球1只7分。
选手需要使用球杆撞击母球去击打目标球来完成得分,每局开始时总是先从红球开始。击球顺序为先打进红球(每次击打允许多个红球同时落袋),然后必须任意指定一个目标彩球击打,如果该彩球被打进(打进后需要再摆回),然后接着击打红球,直到红球全部落袋,然后以黄、绿、咖啡、蓝、粉红、黑的顺序逐个击球(不再摆回),最后以得分高者为胜。任何时候红球落袋都不再摆回,任何时候因犯规导致彩球落袋,彩球必须摆回。
斯诺克比赛由双方轮流击打,必须击打合规的目标球,打进则本方得到相应的分数并继续击打,未打进或犯规轮换为对方击打,未打进不得分,犯规将进行罚分处理。
犯规规则如下:
1. 当击打目标球时,如果先击打到或同时击打到一个或多个其他颜色的球,或者有其他颜色的球落袋,或者打空(未击打到任何球),则视为犯规。此时需要比较目标球的分值和与本犯规相关的其他颜色的球的分值,取其中最高的分值,如果该分值小于4,则对方加4分,否则对方加该分值。
2. 当击打红球落袋后,继续击打任意彩球时打空,即未打击到任何球,对方加4分。
相比正式的斯诺克比赛,本问题对规则进行了简化,任何时候都可以结束比赛并计算比赛结果,不考虑白球落袋的情况。
信息化时代的智能台球桌能自动记录实际比赛时的击打记录,并传送到后台,但该记录仅仅是流水记录,并且无参赛选手的任何信息,需要你编程计算每场比赛的比分,同时需要计算单杆100分及以上的情况(单杆得分是指选手一次连续击打所得分数之和)。
【输入形式】
输入第一行为正整数t (t≤100),表示有t组测试数据,每组数据代表一局比赛。
在输入中,球的颜色表示为:
r-红色球 y-黄色球 g-绿色球 c-咖啡色球 b-蓝色球 p-粉红球 B-黑色球
接下来的每组数据包括若干行,每一行为一次击打的结果,为智能球桌记录下来的流水记录,每组数据最后一行为-1,表示每组数据的结束。
流水记录包含用空格分隔的2个部分:
首先撞到的球 落袋球及数量
第一部分“首先撞到的球”为一个字符串,可以是“rygcbpB”中1个或多个字符组合(可能有多个字符“r”),或为字符串“NULL”。为“NULL”时,第二部分必为空,表示该次击打未撞击到任何球也没有任何球落袋。当红球落袋后继续击打任意彩球时,该部分为“ygcbpB”中的任意单个字符时都认为是合规的目标球。
第二部分“落袋球及数量”为一个字符串,例如“r2gb”,代表本次击打有两个红球落袋,以及绿球和篮球落袋,红色球r后面有数字(大于0小于16),表示红球的落袋数,其他彩球后无数字。该部分可以为空,表示本次击打无球落袋。
比赛在A与B之间进行,每局比赛总是由A先开球。
【输出形式】
输出为t+1行,前t行每行输出用冒号分隔的两个整数,表示每局比赛A与B之间的比分;最后一行输出用冒号分隔的两个整数,表示t局比赛之后A与B之间获得的单杆100分及以上的次数之比(单杆得分是指选手一次连续击打所得分数之和)。
【样例输入】
3 r r1 B r r2 c c r r1 b g -1 rp r1 r br2B NULL r r12 y y g p -1 rr r3 NULL r r1 yg y -1
【样例输出】
6:7 13:24 7:5 0:0
【样例说明】
第一局比赛:
A击打红球,打进1个红球,得1分,比分为 1:0
A继续击打任意彩球,打到黑球,未打进,不得分,比分为1:0
轮换为B击打红球,打进两个红球,得2分,比分为1:2
B继续击打任意彩球,打到咖啡球,打进咖啡球,咖啡球摆回,得4分,比分为1:6
B继续击打红球,打进一个红球,得1分,比分为1:7
B继续击打任意彩球,打到蓝球,打进绿球,犯规,取分值最大者蓝球,绿球摆回,对方加5分,比分为6:7
-1比赛结束
第二局比赛:
A击打红球,首先打到红球和粉球,犯规,打进1个红球和咖啡球,犯规,咖啡球摆回,取分值最大的粉球,对方加6分,比分为0:6
B击打红球,首先打到红球,打进蓝球、2个红球和黑球,犯规,蓝球和黑球摆回,取分值最大的黑球,对方加7分,比分为7:6
A击打红球,未打到任何球,犯规,对方加4分,比分为7:10
B击打红球,打到红球,打进12个红球,加12分,比分为7:22
B击打任意彩球,打到黄球,打进黄球,黄球摆回,得2分,比分为7:24
B击打黄球,打到绿球,打进粉球,犯规,粉球摆回,对方加6分,比分为13:24
-1比赛结束
第三局比赛:
A击打红球,打到2个红球,打进3个红球,加3分,比分为3:0
A击打任意彩球,打空,未打到任何球,对方加4分,比分为3:4
B击打红球,打到1个红球,打进1个红球,加1分,比分为3:5
B击打任意彩球,打到黄球和绿球,打进黄球,犯规,黄球摆回,取分值最高的绿球,绿球分值小于4,对方加4分,比分为7:5
-1比赛结束
3局比赛中无人单杆得分过100,最后一行输出0:0
【解题思路】
这是我写出来的第一个大模拟,发个博客纪念一下。
题目涉及到的情况还是比较多的,需要耐心看题。有以下几种情况需要特别注意:
(1) 选手在击球之后是有可能没办法将任何球击落入袋的,如样例输入的第三行就只有一个B,意思是选手击打黑球之后并没有任何球入袋。
(2) 选手如果击空(NULL)之后,是需要换人上场的。
(3) 对于红球,不管选手是否犯规,只要入袋,则不被拿回,所以我们还需要特别判断击入袋中的球是否有红球,如果有,我们要将击入袋的红球一并减掉。
(4) 选手如果成功击打红球,此时若恰好将红球全部击打入袋,即红球数量为0,此时仍需进行一次自由击打彩球,而后再进行顺序击打彩球。
【代码分析】
使用string来存储输入的比赛记录,将所有比赛记录放入list中来维护(这里换成queue更好),而后再来一步一步判断比赛情况。
涉及到A和B交替发球,所以这里使用一个 ret 变量来判断是A起手发球还是B起手发球,这样做可以有效避免代码冗长的问题。
play函数是用来模拟选手发球的情况,按照题目意思,选手先打红球,若此时选手犯规,则换人;若不犯规,则自由击打彩球,若犯规,则换人;若不犯规,判断红球个数,若红球为0,则顺序击打彩球,若红球不为0,则继续击打红球(代码当中使用了goto语句来处理这个问题)
play_color函数是用来模拟顺序击打彩球的情况,使用 list 来维护彩球,实际上可以使用queue,毕竟先进先出。
check函数用来判断是否犯规(或者理解为判断是否换人),根据题目意思,进行相对应的犯规判断即可,要注意的是对之前在【解题思路】里面说过的特殊情况要做特别判断。
还有一个需要注意的地方就是函数在编写的时候注意进行截断,我一开始有一个地方忘记做截断导致多判断了几次。还有就是要注意初始化,进行数据存储的数据结构在使用之前要清空。
【代码实现】
#include<iostream>
#include<string>
#include<list>
using namespace std;
int n,ret=1,red=15,scoreA=0,scoreB=0,hitA=0,hitB=0,loneA=0,loneB=0;
//ret=1,A起手,scoreAB是AB的比赛分数,hitAB是单杆分数,loneAB是单杆超100分次数
list<string> l; //存储比赛记录
list<char> color_l; //彩球击打顺序
void init_list(list<char> &color_l) {
//黄、绿、咖啡、蓝、粉红、黑
color_l.push_back('y');
color_l.push_back('g');
color_l.push_back('c');
color_l.push_back('b');
color_l.push_back('p');
color_l.push_back('B');
}
int get_ballScore(char ball) {
switch(ball) {
case 'r':{
return 1;
break;
}
case 'y':{
return 2;
break;
}
case 'g':{
return 3;
break;
}
case 'c':{
return 4;
break;
}
case 'b':{
return 5;
break;
}
case 'p':{
return 6;
break;
}
case 'B':{
return 7;
break;
}
}
}
bool check(string s, char goal) { //比赛记录,目标球
if(s=="NULL") { //打空直接犯规
if(ret==1) { //记录分数,给对方加分
scoreB+=(get_ballScore(goal)>4?get_ballScore(goal):4);
}
else if(ret==-1) {
scoreA+=(get_ballScore(goal)>4?get_ballScore(goal):4);
}
return true;
}
string temp=s+" ",left="",right=""; //left为击球情况,right为落球情况
int pos=temp.find(" ");
left=temp.substr(0,pos);
if(pos!=s.size()) { //打中球但是落空的情况要特判
right=s.substr(pos+1);
}
int num=0;
if(right!="") { //先处理红球落袋的情况,不管是否犯规,红球一落袋,就不能放回
for(int i=0; i<right.size(); i++) {
if(right[i]=='r') {
for(int j=i+1; j<right.size(); j++) {
if(!(right[j]>='0'&&right[j]<='9')) {
break;
}
num=num*10+right[j]-'0';
}
red-=num;
break;
}
}
}
bool flag=false; //判断是否有别的球被击打或者落袋
for(int i=0; i<left.size(); i++) { //先来判断击打情况中是否有别的球
if(!(left[i]>='0'&&left[i]<='9')&&left[i]!=goal) { //有别的球
flag=true;
}
}
if(right!="") { //再判断落袋情况中是否有别的球
for(int i=0; i<right.size(); i++) {
if(!(right[i]>='0'&&right[i]<='9')&&right[i]!=goal) {
flag=true;
}
}
}
if(flag) {
int tempScore=0;
for(int j=0; j<s.size(); j++) { // ?红球是算1分还是算num分?
if(!((s[j]>='0'&&s[j]<='9')||s[j]==' ')) {
if(get_ballScore(s[j])>tempScore) {
tempScore=get_ballScore(s[j]);
}
}
}
if(ret==1) { //记录分数,给对方加分
scoreB+=(tempScore>4?tempScore:4);
}
else if(ret==-1) {
scoreA+=(tempScore>4?tempScore:4);
}
return true;
}
if(right=="") { //未打进最终还是要换人
return true;
}
return false;
}
void getScore(string s, char goal) {
//优先判定不犯规但是没有球落袋的情况
string temp=s+" ",left="",right=""; //left为击球情况,right为落球情况
int pos=temp.find(" ");
left=temp.substr(0,pos);
if(pos!=s.size()) { //打中球但是落空的情况要特判
right=s.substr(pos+1);
}
if(right=="") {
return; //没有球落袋,不做任何处理
}
int sum=0;
if(goal=='r') {
for(int i=1; i<right.size(); i++) {
sum=sum*10+right[i]-'0';
}
}
else {
sum=get_ballScore(goal);
}
//总分进行累加
if(ret==1) {
scoreA+=sum;
hitA+=sum;
}
else if(ret==-1) {
scoreB+=sum;
hitB+=sum;
}
}
void change(int Ret) {
if(Ret==1) { //从A换到B
if(hitA>=100) {
loneA++;
}
hitA=0;
}
else if(Ret==-1) { //从B换到A
if(hitB>=100) {
loneB++;
}
hitB=0;
}
}
void play_color(int* Ret) {
if(l.empty()) {
return;
}
string s=l.front();
l.pop_front();
if(check(s,color_l.front())) { //犯规了就换人
change(ret); //计算单杆得分
*Ret=-*Ret;
play_color(&ret);
return;
}
getScore(s,color_l.front()); //自己得分
color_l.pop_front(); //没有犯规就弹出一个彩球
play_color(&ret);
}
void play(int* Ret) {
if(l.empty()) { //比赛打完了
return;
}
if(red==0) {
play_color(&ret);
return;
}
AGAIN:
string s=l.front(); //拿取第一条比赛记录
l.pop_front();
//打红球,先确定是否犯规
if(check(s,'r')) {
change(ret); //计算单杆得分
*Ret=-*Ret; //换人
play(&ret);
return;
}
getScore(s,'r'); //自己得分
if(l.empty()) {
return;
}
s=l.front();
l.pop_front(); //接下来打任意彩球,先判定是否犯规
if(s=="NULL") { //打红球后任意彩球打空,对方加4分
if(*Ret==1) {
scoreB+=4;
}
else if(*Ret==-1) {
scoreA+=4;
}
change(ret); //计算单杆得分
*Ret=-*Ret;
play(&ret);
return;
}
if(check(s,s[0])) { //把第一个打到的彩球作为目标球
change(ret); //计算单杆得分
*Ret=-*Ret;
play(&ret);
return;
}
getScore(s,s[0]);
if(l.empty()) {
return;
}
if(red==0) {
play_color(&ret);
return;
}
//如果红球还有剩余,则重复上述过程
goto AGAIN;
}
int main() {
string s="";
cin>>n;
cin.get();
for(int i=0; i<n; i++) {
l.clear();
color_l.clear();
init_list(color_l); //注意初始化
red=15; //红球一共有15个
scoreA=scoreB=hitA=hitB=0; //A与B开始得分为0
ret=1; //A先起手
while(getline(cin,s)) {
if(s=="-1") {
break;
}
l.push_back(s); //对比赛信息进行记录
}
//对储存好的比赛记录进行得分模拟
play(&ret);
if(hitA>=100) {
loneA++;
}
if(hitB>=100) {
loneB++;
}
cout<<scoreA<<":"<<scoreB<<endl;
}
cout<<loneA<<":"<<loneB<<endl;
return 0;
}