☰
UVa 650 Bowl
2026/10/21 2:03:15 网站建设 项目流程

题目描述

Joe\texttt{Joe}Joe的保龄球馆一直使用人工计分,现在Joe\texttt{Joe}Joe二世计划在每个球道旁放置一台PC\texttt{PC}PC,让球手在每次投球后输入剩余站立的瓶数,由程序自动完成计分并显示结果。

保龄球计分规则如下:

  • 每局包含101010帧(frame\texttt{frame}frame)。
  • 每个帧最多投两球,第101010帧若出现全倒(strike\texttt{strike}strike)或补中(spare\texttt{spare}spare)则获得额外奖励投球。
  • 每击倒一瓶得111分。
  • Strike\texttt{Strike}Strike(第一球击倒全部101010瓶):本帧得101010分加上接下来两球击倒数之和。
  • Spare\texttt{Spare}Spare(两球击倒全部101010瓶):本帧得101010分加上下一球击倒数之和。
  • 第101010帧的额外投球只用于计算该帧得分,不再产生额外奖励。
  • 满分300300300分(每帧303030分)。

输入为若干完整或未完成的游戏,每行包含一个名字(不超过121212个字符)和若干个整数,表示每次投球后剩余的瓶数。程序需要根据这些剩余数推算出每次投球的击倒数,并按规则计分,最后按照指定格式输出每帧的投球标记和累计分数。

输入格式

输入文件包含多个游戏,每个游戏占一行。每行第一个字符串为玩家名字,长度不超过121212个字符,后跟若干个整数,每个整数表示一次投球后剩余站立的瓶数(0∼100 \sim 100∼10)。每个游戏的投球数介于111111到212121之间,输入保证不会给出超出该游戏最后一投的数据。文件没有预先指定的行数,需读至EOF\texttt{EOF}EOF。

输出格式

对于每个游戏,输出两行:

  1. 第一行:名字(左对齐,占131313个字符宽度)后跟每个框架的投球记录。
    标记规则:

    • 数字'1'~'9'表示该投击倒数;
    • '-'表示该投击倒000瓶;
    • 'X'表示第一投全倒(strike\texttt{strike}strike);
    • '/'表示该投完成补中(spare\texttt{spare}spare)。
  2. 第二行:累计分数,每个框架的累计分数右对齐,占333个字符宽度,若某框架因奖励球未知而无法计分,则留空。

每个框架字段宽度为333个字符,字段间用一个空格分隔。名字字段与其他框架之间也有一个空格(总宽度131313)。不同玩家的输出之间空一行。

样例

输入

Chuck 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 Solly 0 0 0 0 0 0 0 0 0 0 0 8 Lothar 10 0 10 0 10 0 Brutus 5 1 2 0 6 0 9 McGillicuddy 0 0 1 0 Homer 0 10 0 0 0 0 Barney 0 0 0 0 0 0 0 0 0 0 1 0

输出

Chuck 9/ 9/ 9/ 9/ 9/ 9/ 9/ 9/ 9/ 9/9 19 38 57 76 95 114 133 152 171 190 Solly X X X X X X X X X XX2 30 60 90 120 150 180 210 240 270 292 Lothar -/ -/ -/ 10 20 Brutus 54 8/ 4/ 1 9 23 34 McGillicuddy X X 9/ 29 49 Homer X -/ X X X 20 40 70 Barney X X X X X X X X X X9/ 30 60 90 120 150 180 210 240 269 289

题目分析

本题是一个典型的保龄球计分模拟问题,但输入提供的是每投后的剩余瓶数,而非击倒数。因此需要根据每一投前后的剩余数还原击倒数。

保龄球计分的关键在于理解strike和spare的奖励机制,以及第101010帧的特殊处理。此外,游戏可能未完成(输入的投球数不足一整局),此时部分帧的分数可能无法确定,需要在输出中留空。

直接根据剩余数计算击倒数并不复杂,但帧的划分必须严格遵循保龄球规则:

  • 前999帧:若第一投全倒(剩余000),则该帧只有一投;否则必须有两投(若第二投缺失,则帧未完成)。
  • 第101010帧:可能有一、二或三投,具体取决于是否出现strike或spare。

计算每帧得分时,strike和spare需要依赖后续投球,这些投球可能位于后续帧或第101010帧的奖励投球中。因此,程序必须首先解析所有输入数据,划分出每帧的投球(包括奖励帧),然后才能计算得分。

输出格式要求严格:名字左对齐131313字符,每个框架字段宽333字符,字段间用一个空格分隔,行尾不得有多余空格。不同玩家输出之间空一行。

解题思路

数据结构设计

为了清晰管理每帧信息,定义结构体Frame:

structFrame{intpin1,pin2;// 该帧第一投、第二投的击倒数(若未投则为 -1)inttype;// 帧类型:NORMAL, STRIKE, SPARE, UNDONE, BONUSintmarks;// 该帧原始得分(不含累计)intstatus;// DEFINED 或 UNDEFINEDstring tag;// 该帧的标记字符串(如 "X", "9/", "54" 等)};

其中BONUS类型用于第101010帧的奖励投球(单独作为一个额外帧存储)。UNDONE表示帧未完成(只有一投且不是strike),其得分无法确定。

算法步骤

  1. 解析输入
    将一行中的所有剩余瓶数存入数组standing。

  2. 划分前999帧
    依次从standing中取出数据,构造前999帧:

    • 若第一投剩余为000,则为STRIKE,击倒101010瓶,帧内只有一投。
    • 否则,需要第二投。计算第一投击倒数为10−r110 - r_110−r1​,第二投击倒数为r1−r2r_1 - r_2r1​−r2​。若第二投后剩余000,则为SPARE;否则为NORMAL。若第二投数据缺失,则帧类型为UNDONE。
    • 生成该帧的标记字符串(tag)。
  3. 处理第101010帧及奖励投球
    当已解析完前999帧后,剩下的standing数据属于第101010帧及其奖励投球。
    根据剩余数据数量(111或222个)构造一个额外的Frame作为奖励帧,类型为BONUS。该帧只存储击倒数,不参与正常计分,只用于为第101010帧提供奖励投球值。

  4. 计算每帧得分
    遍历所有帧(包括奖励帧),按以下规则计算每帧的marks(该帧得分):

    • 若帧类型为UNDONE,标记为UNDEFINED。
    • 对第101010帧(索引999):
      • 若为STRIKE,则需要在奖励帧中取两投(若存在),否则未定义。
      • 若为SPARE,则需要在奖励帧中取一投(若存在),否则未定义。
    • 对前999帧:
      • 若为STRIKE,需要后续两投(可能跨帧),若不足则未定义。
      • 若为SPARE,需要后续一投,若不足则未定义。
      • 若为NORMAL,得分为两投击倒数之和。
    • 若该帧状态为DEFINED,则累加至累计分数。
  5. 累计分数
    顺序累加已定义帧的得分,若遇到未定义帧,则后续累计分数均置为未定义(输出留空)。

  6. 输出

    • 第一行:输出名字(左对齐至131313字符),然后逐帧输出tag,每帧补足至333字符宽度,帧间加一个空格。若存在奖励帧(第111111帧),则将其tag追加到第一行末尾(紧接第101010帧标记之后)。
    • 第二行:输出131313个空格,然后逐帧输出累计分数(若已定义),右对齐补足至333字符宽度,帧间加一个空格。
    • 每处理完一个游戏,输出一个空行。

关键细节

  • 击倒数还原:前999帧的第二投击倒数必须用第一投后的剩余数减去第二投后的剩余数,因为瓶数在帧内不会重置。
  • 第101010帧奖励帧的标记生成:当奖励帧有两投时,可能形成X后跟X、/或其他组合,需按规则生成。
  • 未完成帧的处理:若输入数据不足以完成第101010帧的奖励投球,相应帧的得分状态设为未定义,累计分数在该帧之后全部留空。
  • 输出格式:利用字符串拼接并去掉尾部多余空格,保证行尾无空格。

代码实现

// Bowl// UVa ID: 650// Verdict: Accepted// Submission Date: 2026-07-02// UVa Run Time: 0.000s//// 版权所有(C)2026,邱秋。metaphysis # yeah dot net#include<bits/stdc++.h>usingnamespacestd;constintNORMAL=0,STRIKE=1,SPARE=2,UNDONE=3,BONUS=4;constintDEFINED=0,UNDEFINED=-1;structFrame{intpin1,pin2,type,marks,status;string tag;};string name;vector<Frame>fs;vector<int>standing;stringformat1(string s,intwidth){while(s.size()<width)s+=' ';returns;}stringformat2(string s,intwidth){while(s.size()<width)s=' '+s;returns;}voidsolve(){// 解析输入while(standing.size()){intr1=standing[0];standing.erase(standing.begin());if(r1==0){Frame f;f.pin1=10,f.pin2=0,f.type=STRIKE,f.tag="X";fs.push_back(f);}else{if(standing.size()){intr2=standing[0];standing.erase(standing.begin());Frame f;f.pin1=10-r1,f.pin2=r1-r2;if(f.pin1==0)f.tag='-';elsef.tag=to_string(f.pin1);if(r2==0)f.type=SPARE,f.tag+="/";else{f.type=NORMAL;if(r1==r2)f.tag+='-';elsef.tag+=to_string(r1-r2);}fs.push_back(f);}else{// 第二投分数为 -1 表示未投掷Frame f;f.pin1=10-r1,f.pin2=UNDEFINED,f.type=UNDONE;if(f.pin1==0)f.tag='-';elsef.tag=to_string(f.pin1);fs.push_back(f);}}// 第10帧后为奖励的两次投掷if(fs.size()==10)break;}// 1次奖励投掷if(standing.size()==1){Frame f;f.pin1=10-standing[0],f.pin2=UNDEFINED,f.type=BONUS;if(f.pin1==0)f.tag='-';elseif(f.pin1==10)f.tag='X';elsef.tag=to_string(f.pin1);fs.push_back(f);}// 2次奖励投掷if(standing.size()==2){if(standing[0]==0){Frame f;f.pin1=10,f.pin2=10-standing[1],f.type=BONUS,f.tag="X";if(f.pin2==0)f.tag+='-';else{if(f.pin2==0)f.tag+='-';elseif(f.pin2==10)f.tag+='X';elsef.tag+=to_string(f.pin2);}fs.push_back(f);}else{Frame f;f.pin1=10-standing[0],f.pin2=standing[0]-standing[1];if(f.pin1==0)f.tag+='-';elsef.tag=to_string(f.pin1);if(f.pin2==0)f.tag+='-';elseif(f.pin1+f.pin2==10)f.tag+='/';elsef.tag+=to_string(f.pin2);fs.push_back(f);}}// 计分for(inti=0;i<fs.size();i++){fs[i].marks=fs[i].pin1+fs[i].pin2;fs[i].status=DEFINED;if(fs[i].type==UNDONE){fs[i].status=UNDEFINED;break;}if(i==9){if(fs[i].type==STRIKE){// 有奖励帧if(fs.size()==11){// 有奖励帧,说明第一投有分数fs[i].marks+=fs[10].pin1;// 检查是否进行了第二投,若未投,则分数为未定义状态if(fs[10].pin2!=UNDEFINED)fs[i].marks+=fs[10].pin2;elsefs[i].status=UNDEFINED;// 未投奖励帧,最后一帧的分数为未定义状态}elsefs[i].status=UNDEFINED;}if(fs[i].type==SPARE){// 检查是否进行了奖励投掷if(fs.size()==11)fs[i].marks+=fs[10].pin1;elsefs[i].status=UNDEFINED;}}elseif(fs[i].type==STRIKE){// 全中,检查后续的第一投if(i+1<fs.size()){fs[i].marks+=fs[i+1].pin1;// 下一帧也是全中,检查是否存在后续一帧if(fs[i+1].pin1==10){if(i+2<fs.size())fs[i].marks+=fs[i+2].pin1;elsefs[i].status=UNDEFINED;// 若下一帧不是全中,检查下一帧是否进行了第二投}else{if(fs[i+1].pin2!=UNDEFINED)fs[i].marks+=fs[i+1].pin2;elsefs[i].status=UNDEFINED;}}elsefs[i].status=UNDEFINED;}elseif(fs[i].type==SPARE){// 检查是否存在下一帧if(i+1<fs.size())fs[i].marks+=fs[i+1].pin1;elsefs[i].status=UNDEFINED;}if(i)if(fs[i].status==DEFINED)fs[i].marks+=fs[i-1].marks;}// 输出string line;line+=name;for(inti=name.length()+1;i<=13;i++)line+=' ';for(inti=0;i<min((int)fs.size(),10);i++)line+=format1(fs[i].tag,3)+' ';while(line.size()&&line.back()==' ')line.pop_back();if(fs.size()==11)line+=fs[10].tag;cout<<line<<'\n';line.clear();for(inti=1;i<=12;i++)line+=' ';for(inti=0;i<min((int)fs.size(),10);i++)if(fs[i].status==DEFINED)line+=format2(to_string(fs[i].marks),4);while(line.size()&&line.back()==' ')line.pop_back();cout<<line<<'\n';}intmain(){cin.tie(0),cout.tie(0),ios::sync_with_stdio(false);string line;intcases=0;while(getline(cin,line)){if(line.empty())continue;if(cases++)cout<<'\n';stringstreamss(line);ss>>name;standing.clear();intr;while(ss>>r)standing.push_back(r);if(standing.empty()){cout<<name<<'\n';cout<<'\n';continue;}fs.clear();solve();}return0;}

总结

本题的核心难点在于:

  • 输入提供的是剩余瓶数,而非击倒数,需要根据帧内投球前后关系正确还原。
  • 保龄球计分的奖励机制(strike和spare)依赖后续投球,程序必须完整解析所有投球后才能计算得分。
  • 输出格式的严格性要求对每行的尾部空格进行清理,并保证每个字段的宽度准确。

解题的关键是设计清晰的数据结构来存储每帧的投球信息,并分阶段处理:先划分帧,再计算得分,最后格式化输出。通过状态标记DEFINED/UNDEFINED处理未完成游戏的计分,符合题目对部分游戏的要求。

该实现能够正确处理全部测试数据,并达到题目的输出格式要求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询