题意
【题面宛如小作文233】
程序设计思维作业和实验使用的实时评测系统,具有及时获得成绩排名的特点,那它的功能是怎么实现的呢?
我们千辛万苦怼完了不忍直视的程序并提交以后,评测系统要么返回AC,要么是返回各种其他的错误,不论是怎样的错法,它总会给你记上一笔,表明你曾经在这儿被坑过,而当你历经千辛终将它AC之后,它便会和你算笔总账,表明这题共错误提交了几次。
在岁月的长河中,你通过的题数虽然越来越多,但通过每题时你所共花去的时间(从最开始算起,直至通过题目时的这段时间)都会被记录下来,作为你曾经奋斗的痕迹。特别的,对于你通过的题目,你曾经的关于这题的每次错误提交都会被算上一定的单位时间罚时,这样一来,你在做出的题数上,可能领先别人很多,但是在做出同样题数的人中,你可能会因为罚时过高而处于排名上的劣势。
例如某次考试一共八道题(A,B,C,D,E,F,G,H),每个人做的题都在对应的题号下有个数量标记,负数表示该学生在该题上有过的错误提交次数但到现在还没有AC,正数表示AC所耗的时间,如果正数a跟上了一对括号,里面有个正数b,则表示该学生AC了这道题,耗去了时间a,同时曾经错误提交了b次。例子可见下方的样例输入与输出部分。
Input
输入数据包含多行,第一行是共有的题数n(1≤n≤12)以及单位罚时m(10≤m≤20),之后的每行数据描述一个学生的信息,首先是学生的用户名(不多于10个字符的字串)其次是所有n道题的得分现状,其描述采用问题描述中的数量标记的格式,见上面的表格。
Output
根据这些学生的得分现状,输出一个实时排名。实时排名显然先按AC题数的多少排,多的在前,再按时间分的多少排,少的在前,如果凑巧前两者都相等,则按名字的字典序排,小的在前。每个学生占一行,输出名字(10个字符宽),做出的题数(2个字符宽,右对齐)和时间分(4个字符宽,右对齐)。名字、题数和时间分相互之间有一个空格。数据保证可按要求的输出格式进行输出。
输入样例
【可复制⬇️】
8 20
GuGuDong 96 -3 40(3) 0 0 1 -8 0
hrz 107 67 -3 0 0 82 0 0
TT 120(3) 30 10(1) -3 0 47 21(2) -2
OMRailgun 0 -99 -8 0 -666 -10086 0 -9999996
yjq -2 37(2) 13 -1 0 113(2) 79(1) -1
Zjm 0 0 57(5) 0 0 99(3) -7 0
输出样例
分析
阅读完小作文之后,会发现这个题目的关键是分析输入流,而分析输入流首先就要考虑如何读取输入流。
- 读取输入流
根据样例可知,程序需要将每一行数据中的单个数据进行分析,而每个数据之间由空格隔开,每个数据还可能会包含非数字字符。
接下来分别分析下我之前WA的做法和修改后AC的两种做法。
- 将整行数据的视作string,逐个字符读入并分析(WA)
string s; //将一行数据视作string类型
// char name[20];
Student a;
cin>>n>>fine;
while( cin>>a.name )
{
// cout<<name<<" ^^ "<<endl;
// a.name=name;
a.information.first=n; //完成题数
a.information.second=0; //总时间
getline(cin,s); //获取一行数据(所有分数信息)
for( int i = 0 ; i < s.size() ; i++ )
{
if( s[i] >= '0' && s[i] <= '9' ) //若当前字符为数字,则进行转换
{
number=s[i]-'0'+number*10; //将字符转换为数字
if( number == 0 ) //若时间为0,完成数-1
a.information.first--;
}
else //若当前为字符
{
switch (s[i])
{
case ' ': //若为空格,则对之前的时间进行结算,并将number清零
time+=number;
number=0;
break;
case '(': //若为左括号
{
time+=number; //将括号前时间先加入总时间中,并将number清零
number=0;
i++;
while( s[i] != ')' ) //将括号中的数字进行转换
{
number=s[i]-'0'+number*10;
i++;
}
number*=fine; //得到总罚时
break;
}
case '-': //若为负号
{
a.information.first--; //完成数-1
while( s[i] != ' ' && i < s.size()-1 ) //跳过该数
i++;
break;
}
default:
break;
}
}
if( i == s.size()-1 )
time+=number;
这种做法有点效仿快读的意思,利用getline一次性读入一行数据到string中,将这一行中的数字和非数字以及空格都视作字符,遍历字符串对每个字符依次进行处理。
- 若当前字符为数字字符,则转换为数字。若转换后为0,对应学生的完成题数-1。
(从字符串中读取字符时总是先读取完整数字的高位,因此不论是读取到该数的第几位数字,转换后所得中间数都不会为0,除非该完整数为0) - 若当前字符为空格,将之前转换所得数纳入总时间
(因为输入的一行数据中每个单项数据结束后一定是空格) - 若当前字符为左括号,先将之前转换所得数纳入总时间。接着继续遍历,将右括号前的所有数字字符进行转换。当遍历到右括号时,结束遍历,并将转换后数字乘以罚时,加入总时间数。之后直接从右括号开始向后遍历。
- 若当前字符为负号,完成题数-1,并让下一次遍历直接从该负号所属的单项数据之后的空格开始。确保下次遍历开始的下标合法。因此若该项数据为整行数据中的最后一个数据,会直接结束遍历。
- 确保遇到任何情况时,若当前遍历到字符串最后一位,都将当前未加入的转换数纳入到总时间中。
从逻辑上讲这种做法似乎没有问题,但是很明显这比较繁琐,是一个笨办法。尽管我反复调试多次,也仍然WA。说明这种笨办法在一些特殊情况下会出现错误(但是我到现在都不知道到底是什么情况😕)。不过这也反映了我对各种输入流处理操作的不熟悉,在长久调试无果的尴尬境况下,只有改变处理方式。
【小tip:若像我一样长时间调试无果,建议重新审视和调整思路和算法,否则可能会浪费很多时间也得不到分】
-
将数字和非数字字符分开单个处理(AC)
-
cin从缓冲区中读入数据,当遇到不可见字符时停止或过滤。
利用cin的这个特性,可以一次从输入数据中读入一个int型数据(必须是有符号数,否则负数中的负号又需要单独处理)。样例中一行数据中除了带括号的单项数据,其余都可以作为int型,根据其正负性进行分别处理。 -
getchar()从缓冲区一次读入一个字符(非数字字符以及空格)。
样例中一行数据中,数字之后是空格或者左括号。若数字之后为空格,则getchar()无法获取任何字符,下一次cin读入空格后的下一个数字。若getchar()取出左括号时,则用cin将其之后的数字读出,单独对其进行处理,最后还应用getchar取掉右括号。
【小tip:数据在输入后会被存在一个缓冲区内,因此读取输入流的操作都是对缓冲区进行操作,不需要从键盘输入获得。cin无法取出空格,而getchar()可以取出空格。但此处cin会自动过滤空格,因此不需要用getchar()取掉空格,下一次cin会直接读入空格之后的数据。】
格式化输出
这个输出搞了半天【手动再见】,在此总结一下。
- setw(int x)
头文件:#include < iomanip >
控制输出宽度,也就是输出一个数据时一共会输出x个字符的宽度。若字符长度小于x,则由空格补齐;若大于x,将字符完整输出。
【小tip:
1、setw(int x)默认右对齐!
2、只对其后紧接的一次输出有效。
比如:cout<<setw(3)<<a<<b<<endl;
其中只有a的输出受setw(3)影响
】
2.左对齐/右对齐
- setiosflags()
头文件:#include < iomanip >
右对齐setiosflags(ios::right)、左对齐setiosflags(ios::left):输出数据在本域宽范围内向右/左对齐,一般与setw()一起使用。
setiosflags()将某个输出格式标志置为 1,而最终的输出格式会以最后一次标识为1的格式为准。
【小tip:
1、若cout<<setiosflags(ios::left)<<setw(2)<<a<<setiosflags(ios::right)<<setw(3)<<b<<endl;
只使用一次,a和b将分别按左对齐和右对齐输出;但若反复使用该操作输出a和b,之后的a不会再按左对齐输出,而是按右对齐输出。
2、只对其后紧接的一次输出有效。
】
- cout<<left
左对齐,在宽度不足时将填充字符添加到右边,一般与setw()一起使用。
【小tip:
1、不会出现setiosflags()中无法重复使用的情况。
2、只对其后紧接的一次输出有效。
3、cout<<right 是c++输出流的默认格式。
】
总结
1.如果比较熟悉哪一种输入输出(c/c++),就要熟知该种输入输出的各种处理操作以及它们的区别,然后熟用。
【cin、cin.get()、getline()、cin.getline()、getchar()、gets()】
【cout各种格式化输出】
2. 长久调试仍然WA的情况需要及时改变思路或算法🤝
代码
//
// main.cpp
// lab2
//
#include <iostream>
#include <map>
#include <string>
#include <cstring>
#include <string.h>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
struct Student
{
pair<int, int> information; //做题信息
char name[20]; //姓名
Student(){}
bool operator < (const Student &s) const //重载<
{
if( information.first != s.information.first ) //首先看完成题数
return information.first > s.information.first;
if( information.second != s.information.second ) //其次看用时
return information.second < s.information.second;
return ( strcmp(name, s.name) < 0 );
}
};
vector<Student> ranking; //学生数组
int main()
{
int n = 0,fine = 0,number = 0;
string s;
// char name[20];
Student a;
cin>>n>>fine;
while( cin>>a.name )
{
// cout<<name<<" ^^ "<<endl;
// a.name=name;
a.information.first=n; //完成题数
a.information.second=0; //总时间
for( int i = 0 ; i < n ; i++ ) //进行n次计算(n道题)
{
cin>>number; //输入数字
if( number <= 0 ) //若小于0则代表未ac,等于0表示没有提交
{
a.information.first--; //完成数-1
continue; //直接进行下一次输入
}
a.information.second+=number; //计算总时间
if( getchar() == '(' ) //从输入中读取字符,若为左括号
{
cin>>number; //再接着读入括号后的数字
number*=fine; //计算总罚时
a.information.second+=number; //讲罚时加入到总时间内
getchar(); //取掉右括号
}
}
ranking.push_back(a); //将当前学生信息插入数组
// cout<<a.information.first<<" ** "<<a.information.second<<endl;
}
// cout<<"----------"<<endl;
sort(ranking.begin(), ranking.end());
for( int i = 0 ; i < ranking.size() ; i++ )
{
cout<<left<<setw(10)<<ranking[i].name<<" ";
//如果用setiosflags(ios::left)会在第二行数据输入的时候就失效,仍然变成右对齐,setw默认右对齐
cout<<setiosflags(ios::right)<<setw(2)<<ranking[i].information.first<<" ";
cout<<setw(4)<<ranking[i].information.second<<endl;
}
return 0;
}
来源:CSDN
作者:孤水
链接:https://blog.csdn.net/weixin_44735997/article/details/104687067