357 lines
20 KiB
Markdown
357 lines
20 KiB
Markdown
# 思路简介
|
||
## 文字描述
|
||
整个ICPC-Management-System服务端存放在命名空间ICPCManager里,其中命名空间ICPCManager::API里面有直接给main函数调用的接口,由API里的函数调用ICPCManager::BackEnd里的函数执行具体操作。
|
||
拆分成ICPCManager::API和ICPCManager::BackEnd的原因有两个:第一个是解析输入命令和校验命令合法性的操作可以在ICPCManager::API里面完成,ICPCManager::BackEnd的函数可以直接从函数参数列表获取数据;第二个是有些对外接口可以拆分成可复用的子命令(虽然在这次大作业中并没有体现,在接口层面就拆分成子命令在编写复杂度上没有明显优势)或不需要执行很多操作(如结束比赛),这样可以降低编写复杂度。
|
||
|
||
在具体的数据维护方面,后台数据(无视是否封榜)的更新较为简单;而与榜单相关的数据则维护哪些数据可以进入榜单、哪些数据可以进入榜单但尚未进入榜单,并注意一些关键细节(如比较器依赖的那些数据的更新要与平衡树的更新实时同步)即可。
|
||
|
||
在性能优化方面,首先完成一些较为明显的优化,如刷新榜单时不完全重构,只更新变化了的数据。然后利用代码性能分析工具gprof寻找占用过多时间的函数,并逐步优化细节。(特别鸣谢:感谢Dark教授传授的卡常技巧)
|
||
## 简图
|
||

|
||
# 测试数据与调试经历
|
||
主要经历了两次重大bug,第一次是在刚开始编写时,出现玄学错误,通过抛出异常等方法逐步排查最终发现是解析命令字符串时出错。第二次是在编写中期,排名维护反复出bug,用下发的测试数据分析,发现bug出在需要依次比较两个得分、罚时均相同的队伍的通过时间序列时,由于下发测试数据过大不便调试,因此我针对性地设计了一组测试数据`testcases/1.in`,并借助它修复了这个bug。在性能优化阶段,在重构刷新榜单函数使得它支持精确更新后,排名维护再次出bug,我同样借助`testcases/1.in`修复了bug。
|
||
___
|
||
# 复杂度分析报告
|
||
## 添加队伍
|
||
O(1),观察代码,AddTeam函数只初始化了后台数据(用随机访问数组存储),因此O(1)
|
||
## 提交题目
|
||
O(1),观察代码,AddTeam函数只更新了后台数据并往暂存区简单地打tag,因此O(1)
|
||
## 刷新榜单
|
||
O(n log n),观察代码,在刷新榜单函数中最多有N个队伍被更新,每次更新进行常数次查询、插入(O(log n)),因此O(n log n)
|
||
## 滚榜
|
||
O(n log n),观察代码,滚榜函数一头一尾各调用一次刷新榜单操作,其中第一次同上述分析O(n log n),第二次只遍历,均摊O(n)。滚榜自身,scroll_cursor保证了每个队伍的每道题最多被处理一次、最外层大循环最多进行N*26次,每次大循环进行常数次查询、插入(O(log n))。综上,滚榜操作O(n log n)
|
||
## 查询队伍排名
|
||
O(1),观察代码,相关函数直接查询跟随榜单刷新的一个随机访问数组,显然O(1)
|
||
## 查询队伍提交情况
|
||
O(1),观察代码,相关函数直接查询后台数据,显然O(1)
|
||
## 特殊说明
|
||
在题目所给的数据范围内,unordered_map时间复杂度退化的可能性可以忽略不计,相关操作视作O(1)
|
||
___
|
||
# :trophy:ICPC Management System
|
||
|
||
> ACM 班 2023 级程序设计课程第二次大作业
|
||
|
||
## 📖目录
|
||
|
||
- [:trophy:ICPC Management System](#trophyicpc-management-system)
|
||
- [📖目录](#目录)
|
||
- [🎈简介](#简介)
|
||
- [背景](#背景)
|
||
- [作业目标](#作业目标)
|
||
- [📝作业说明](#作业说明)
|
||
- [🛎分数组成](#分数组成)
|
||
- [A 班](#a-班)
|
||
- [B 班](#b-班)
|
||
- [⏱截止时间](#截止时间)
|
||
- [A 班](#a-班-1)
|
||
- [B 班](#b-班-1)
|
||
- [🚀作业要求](#作业要求)
|
||
- [术语解释](#术语解释)
|
||
- [指令说明](#指令说明)
|
||
- [输入格式](#输入格式)
|
||
- [输出格式](#输出格式)
|
||
- [数据范围](#数据范围)
|
||
|
||
## 🎈简介
|
||
|
||
### 背景
|
||
|
||
**ICPC** (国际大学生程序设计竞赛)由 ICPC 基金会举办的一项旨在展示大学生创新能力、团队精神和在压力下编写程序、分析和解决问题能力的年度竞赛,是最具影响力的大学生计算机竞赛。在 ICPC 比赛中,每个队伍尝试在错误提交次数最少的情况下解答出最多的问题,获胜者为正确解答题目最多且总罚时最少的队伍。
|
||
|
||
### 作业目标
|
||
|
||
我们想通过本次作业达到以下目标:
|
||
|
||
- 学会使用 STL 库
|
||
- 加深对于字符串处理的掌握
|
||
- 加深对于时间复杂度的理解
|
||
- 提高模拟水平,掌握基本的拆分功能、规划项目的能力
|
||
- 学会处理边界情况
|
||
- 规范代码风格
|
||
- 学会自己设计测试数据
|
||
|
||
因此,你需要实现一个 ICPC 比赛后台系统,通过队伍的提交情况对比赛结果进行维护,具体要求实现的操作见 [作业要求](#作业要求)。
|
||
|
||
## 📝作业说明
|
||
|
||
### 🛎分数组成
|
||
|
||
为了让大家在更好地熟悉 C++ STL 相关操作,B 班同学需要先完成两个前置作业,A 班同学不做要求。
|
||
|
||
此外,为确保大家有合理的时间分配,我们对 B 班同学设有中期检查,A 班同学不做要求。
|
||
|
||
因此 A、B 班的分数组成略有差别:
|
||
|
||
#### A 班
|
||
|
||
| 得分项 | 分数占比 |
|
||
| :----------------------------------------------------------: | :------: |
|
||
| 通过 **1986. ICPC Management System (2023 A)** 和 **复杂度分析报告提交** | 80% |
|
||
| Code Review | 20% |
|
||
|
||
#### B 班
|
||
|
||
| 得分项 | 分数占比 |
|
||
| :----------------------------------------------------------: | :------: |
|
||
| 通过 STL 练习 **1383. 聪老师不想摸鱼** | 5% |
|
||
| 通过前置作业 **1346. Michelle 的学生会工作** | 15% |
|
||
| 通过 **1987. ICPC Management System (2023 B)** 和 **复杂度分析报告提交** | 60% |
|
||
| 中期检查 | 5% |
|
||
| Code Review | 15% |
|
||
|
||
下面是需要说明的几点:
|
||
|
||
- 题目 ICPC Management System (2023 B) 的所有测试点的时间限制是 ICPC Management System (2023 A) 的 **4 倍**。
|
||
|
||
- 为了帮助大家养成更好的开发习惯,在 **中期检查** 时请以代码结构图等方式向助教讲解你的 **实现思路**。同时,我们也会检查大家的 **完成度**,请注意**时间分配**。
|
||
|
||
- 在 Code Review 中我们会 **严格审查你的代码风格**,请遵循[代码风格要求](https://acm.sjtu.edu.cn/wiki/C%2B%2B代码风格)。
|
||
|
||
- 为了训练大家分析时间复杂度的能力,本次作业要求完成一份 **书面复杂度分析报告**,报告提交的截止时间与 OJ 提交的截止时间相同,且在 Code Review 时需要展示你的报告。报告的准确程度会影响你的 Code Review 得分。在报告里,你需要给出你的代码中以下几条指令的时间复杂度,并简要说明你是怎么分析的。设 $N$ 为队伍总数,指令后方标注的是我们所能接受的 **最坏** 时间复杂度(部分指令存在更优方案),如果你的程序对应部分的时间复杂度不能满足要求,Code Review 时将被扣除部分分数。
|
||
- 添加队伍 $O(\log N)$
|
||
- 提交题目 $O(\log N)$
|
||
- 刷新榜单 $O(N\log N)$
|
||
- 滚榜 $O(N\log N)$
|
||
- 查询队伍排名 $O(\log N)$
|
||
- 查询队伍提交情况 $O(\log N)$
|
||
|
||
报告形式:建议使用 Markdown 编写,这样可以方便地编写数学公式和插入代码。
|
||
|
||
- 本次作业提供部分样例数据供同学们测试使用,数据存放在本仓库 `data` 目录下。注意,这些数据并非 Online Judge 上的测试数据点,本地通过所有数据点并不能保证你能够通过 OJ 上的测试。每位同学有 **一次** 机会从负责本次大作业的助教(李心瑶 和 薛佳锐)那里获得一个 OJ 测试数据点,**同时禁止与同学分享你获得的数据点**。
|
||
|
||
- 除本地提供的样例数据外,我们也鼓励同学们根据自己的程序逻辑,尝试自己设计测试数据辅助调试。在开发和调试的过程中,你可以 **保存下自己设计过的测试数据** ,并记录下自己遇到过哪些困难的问题,以及最终是如何解决的,我们将会**在 Code Review 时检查**这些内容。
|
||
|
||
- 本次作业的ddl为死ddl,原则上不接受ddl后的提交,但如果你没有能够在ddl前通过对应的题目,你也可以通过提交低一档的题目来获得一定的分数,具体如下
|
||
- A 班同学如果未能通过**1986. ICPC Management System (2023 A)**,但通过了**1987. ICPC Management System (2023 B)**,将会获得 **不少于60%** 的分数。
|
||
- B 班同学如果未能通过**1987. ICPC Management System (2023 B)**,但通过了去年的题目**1731. ICPC Management System (2022 B)**,也将会获得 **不少于60%** 的分数。
|
||
|
||
### ⏱截止时间
|
||
|
||
#### A 班
|
||
|
||
- ICPC Management System (2023 A) 与分析报告:第 7 周周一(10 月 23 日)18:00
|
||
|
||
#### B 班
|
||
|
||
- STL 练习:第 6 周周一(10 月 16 日)18:00
|
||
- 前置作业:第 6 周周四(10 月 19 日)20:00
|
||
- 中期检查:第 7 周周二(10 月 24 日)答疑时间
|
||
- ICPC Management System (2023 B) 与分析报告:第 8 周周一(10 月 30 日)18:00
|
||
|
||
## 🚀作业要求
|
||
|
||
### 术语解释
|
||
|
||
由于本次作业涉及到很多专用术语,为了更好地理解后面的指令说明,我们先会对作业中用到的术语进行解释。
|
||
|
||
- **比赛时间**:我们用 `duration_time` 表示比赛的持续时间,比赛时间范围是闭区间 `[1, duration_time]`,因此,我们可以用该区间中的某个整数,表示比赛过程中的某个时间点。我们仅保证输入数据中的提交时间是 **单调不降** 的,这意味着可能出现 **相同** 的时间。
|
||
|
||
- **队伍**:每一支参赛队伍都有自己唯一的队伍名称,队名格式为 20 个字符内(含)的大小写字母、数字及下划线的组合。
|
||
|
||
- **提交**:队伍提交一份解答,经评测机评测后,后台获得这次提交的基本信息。未封榜时的提交会实时更新队伍的状态(如通过题目数量等),但 **并不会更新榜单上队伍的排名**。
|
||
|
||
- **评测状态**:每次提交都有其对应的评测状态,评测状态可能有以下情况:
|
||
|
||
- Accepted
|
||
- Wrong_Answer
|
||
- Runtime_Error
|
||
- Time_Limit_Exceed
|
||
|
||
除了 Accepted 算做通过外,剩下的状态均不算做通过。
|
||
|
||
- **刷新榜单**:更新榜单上队伍的排名。
|
||
|
||
- **榜单**:按照排名从前到后显示每支队伍的状态。
|
||
|
||
- **罚时**:一种比较两队伍排名的参数。一个队伍对于某道题目的罚时定义为 $P = 20X + T$ ,其中 $X$ 为该队伍第一次正确提交前的提交次数, $T$ 为该队通过此题的时间,即第一次正确提交的时间。某队伍的罚时定义为该队伍对于其所有 **通过的题目** 的罚时之和。
|
||
|
||
- **排名**:比赛的排名由多个参数共同决定:
|
||
- 首先,通过题目数多的队伍排名靠前;
|
||
- 当两队通过题目数相同时,我们比较两队的罚时,罚时小者排名靠前;
|
||
- 依旧平局时,我们比较两队通过时间最大的题目的通过时间,最大通过时间较小的一队排名靠前,相同则比较通过时间第二大的题目,依旧相同则比较通过时间第三大的题目……以此类推;
|
||
- 如果依旧平局则比较两支队伍名称的字典序,字典序较小的队伍排名靠前(由于队伍名称是互不相同的,因此两支队伍名称的字典序必有一个小于另一个)。
|
||
- **注意**:以上所有因素均 **不包含冻结状态的题目**(冻结状态见下一条),显而易见,封榜后直到滚榜前排行榜上的排名不会发生改变。
|
||
- 第一次刷新榜单前的排名的依据是队伍名称的字典序。
|
||
|
||
- **封榜**:封榜后,对于任意一支队伍,所有 **封榜前该队伍未通过的题目**,封榜后的排行榜上并不显示实时的提交结果,而只显示出封榜时间段内该队伍对题目的提交次数,封榜后至少有一次提交的题目将变成 **冻结状态**(封榜前通过的题目,封榜后再次提交不会被冻结)。
|
||
|
||
- **滚榜**:滚榜环节中,每次在排行榜上选择一支排名最靠后且有冻结状态题目的队伍,并选择该队伍编号最小的一道冻结状态的题目进行解冻,并重新计算排名,更改排行榜上的排名状态。之后在更新过的排行榜上,再次选择排名最靠后且有冻结状态题目的队伍,重复上述解冻操作,直到所有队伍都不存在仍处于冻结状态的题目。这样,我们就得到了当前正确的榜单。
|
||
- **注意**:与实际比赛不同的是,本次作业中一次比赛内可以进行多次封榜、滚榜。每次滚榜必须在封榜状态下才能执行;滚榜结束后,封榜状态将被解除,之后可以再次封榜。
|
||
|
||
|
||
### 指令说明
|
||
|
||
所有的指令格式由下方的代码框给出,全大写部分代表命令,中括号 `[]` 内的小写部分代表对应参数(括号并不会出现在输入中)。
|
||
|
||
```plain
|
||
# 添加队伍
|
||
ADDTEAM [team_name]
|
||
|
||
# 开始比赛
|
||
START DURATION [duration_time] PROBLEM [problem_count]
|
||
|
||
# 提交题目
|
||
SUBMIT [problem_name] BY [team_name] WITH [submit_status] AT [time]
|
||
|
||
# 刷新榜单
|
||
FLUSH
|
||
|
||
# 封榜
|
||
FREEZE
|
||
|
||
# 滚榜
|
||
SCROLL
|
||
|
||
# 查询队伍排名
|
||
QUERY_RANKING [team_name]
|
||
|
||
# 查询队伍提交情况
|
||
QUERY_SUBMISSION [team_name] WHERE PROBLEM=[problem_name] AND STATUS=[status]
|
||
|
||
# 结束比赛
|
||
END
|
||
```
|
||
|
||
- 添加队伍
|
||
- `ADDTEAM [team_name]`
|
||
- 向系统中添加队伍。
|
||
- 成功添加输出 `[Info]Add successfully.\n`
|
||
- 若比赛已开始,输出 `[Error]Add failed: competition has started.\n`
|
||
- 若比赛未开始但队伍名重复,输出 `[Error]Add failed: duplicated team name.\n`
|
||
|
||
|
||
- 开始比赛
|
||
- `START DURATION [duration_time] PROBLEM [problem_count]`
|
||
- 开始比赛,比赛时间范围为闭区间 `[1, duration_time]` ,题号范围为前 `problem_count` 个大写英文字母。
|
||
- 成功开始则输出 `[Info]Competition starts.\n`
|
||
- 若比赛已开始,输出 `[Error]Start failed: competition has started.\n`
|
||
|
||
**保证以下全部操作均在开始比赛之后出现。**
|
||
|
||
- 提交题目
|
||
|
||
- `SUBMIT [problem_name] BY [team_name] WITH [submit_status] AT [time]`
|
||
- 保证输入合法,记录 `team_name` 在 `time` 时刻对题目 `problem_name` 的一次提交,评测状态为 `submit_status` 。
|
||
- `submit_status` 可能有以下情况:Accepted,Wrong_Answer,Runtime_Error,Time_Limit_Exceed。除了 Accepted 算做通过外,剩下的状态均不算做通过。保证 `time` 按照提交出现的顺序单调(非严格)上升。
|
||
- 本条指令无输出。
|
||
|
||
- 刷新榜单
|
||
|
||
- `FLUSH`
|
||
- 刷新当前榜单。
|
||
- 输出 `[Info]Flush scoreboard.\n`
|
||
|
||
- 封榜
|
||
|
||
- `FREEZE`
|
||
- 进行封榜操作。
|
||
- 成功则输出 `[Info]Freeze scoreboard.\n`
|
||
- 若已封榜但未滚榜,则输出 `[Error]Freeze failed: scoreboard has been frozen.\n`
|
||
|
||
- 滚榜
|
||
|
||
- `SCROLL`
|
||
- 若未封榜,则输出 `[Error]Scroll failed: scoreboard has not been frozen.`
|
||
- 若已封榜,则可以开始滚榜,
|
||
- 首先输出提示语 `[Info]Scroll scoreboard.\n`,
|
||
- 然后输出 **滚榜前** 的榜单,
|
||
- 接着每一行输出滚榜时 **导致排名变化** 的解冻,
|
||
- 最后输出 **滚榜后** 的榜单。
|
||
- 排名变化的输出格式如下:
|
||
|
||
```plain
|
||
[team_name1] [team_name2] [solved_number] [penalty_time]
|
||
```
|
||
|
||
`team_name1` 表示题目解冻导致排名上升的队伍, `team_name2` 表示排名被 `team_name1` 取代的队伍, `solved_number` 和 `penalty_time` 为 `team_name1` 新的解题数和罚时;
|
||
- 榜单的输出格式如下:
|
||
输出 $N$ (队伍总数)行,每行以
|
||
|
||
```plain
|
||
队名 排名 解题数 总罚时 A B C ...
|
||
```
|
||
|
||
的格式表示一支队伍的情况,其中“A B C ...”表示每一题的情况,有以下三种可能:
|
||
|
||
- 该题未被冻结且已经通过:
|
||
- 显示 `+x`,其中 `x` 表示该题第一次通过前错误的次数
|
||
- 如果 `x` 为 0,则显示 `+` 而不是 `+0`
|
||
- 该题未被冻结但没有通过:
|
||
- 显示 `-x`,其中 `x` 表示该题错误的次数
|
||
- 如果 `x` 为 0(即此队伍还没有提交过此题),则显示 `.` 而不是 `-0`
|
||
- 该题被冻结:
|
||
- 显示 `-x/y`,其中 `x` 表示封榜前错误的次数,`y` 表示封榜后的提交次数。
|
||
- 如果 `x` 为 0,则显示 `0/y` 而不是 `-0/y`
|
||
|
||
- 查询队伍排名
|
||
|
||
- `QUERY_RANKING [team_name]`
|
||
|
||
- 查询对应队伍排名。
|
||
|
||
- 若队伍不存在,则输出 `[Error]Query ranking failed: cannot find the team.\n`
|
||
|
||
- 若队伍存在,则输出 `[Info]Complete query ranking.\n`。此时若处在封榜状态,则需要输出一行 `[Warning]Scoreboard is frozen. The ranking may be inaccurate until it were scrolled.\n`。不论是否封榜,输出上一次刷新榜单后的队伍排名,格式如下:
|
||
|
||
```plain
|
||
[team_name] NOW AT RANKING [ranking]
|
||
```
|
||
|
||
- 查询队伍提交情况
|
||
|
||
- `QUERY_SUBMISSION [team_name] WHERE PROBLEM=[problem_name] AND STATUS=[status]`
|
||
|
||
- 查询对应队伍的满足条件的最后一次提交。**封榜后的提交可以被查询到。**
|
||
|
||
以下是可参考以下合法示例:
|
||
|
||
```plain
|
||
# 查询 Team_Rocket 的最后一次提交
|
||
QUERY_SUBMISSION Team_Rocket WHERE PROBLEM=ALL AND STATUS=ALL
|
||
|
||
# 查询 Team_Plasma 的最后一次状态为 Accepted 的提交
|
||
QUERY_SUBMISSION Team_Plasma WHERE PROBLEM=ALL AND STATUS=Accepted
|
||
|
||
# 查询 Pokemon_League 的最后一次对题目 A 的提交
|
||
QUERY_SUBMISSION Pokemon_League WHERE PROBLEM=A AND STATUS=ALL
|
||
|
||
# 查询 Opelucid_Gym 队的最后一次对 M 题状态为 Runtime_Error 的提交
|
||
QUERY_SUBMISSION Opelucid_Gym WHERE PROBLEM=M AND STATUS=Runtime_Error
|
||
```
|
||
|
||
- 若队伍不存在,则输出 `[Error]Query submission failed: cannot find the team.\n`
|
||
|
||
- 若队伍存在 `[Info]Complete query submission.\n`
|
||
|
||
- 若无满足条件的提交,输出 `Cannot find any submission.\n`
|
||
|
||
- 若有满足条件的提交,输出一行表示最后一次满足条件的提交,格式如下:
|
||
|
||
```plain
|
||
[team_name] [problem_name] [status] [time]
|
||
```
|
||
|
||
`problem_name` 为该次提交的题目编号,`status` 为该次提交的状态,`time` 为该次提交的时间。保证询问中的 `problem_name` 与 `status` 格式合法。
|
||
|
||
- 结束比赛
|
||
- `END`
|
||
- 结束比赛。
|
||
- 输出 `[Info]Competition ends.\n`。保证结束比赛时排行榜不处于封榜状态,且之后没有任何操作。
|
||
|
||
### 输入格式
|
||
|
||
- 程序开始运行后,会读入若干个指令,直到读入 `END` 指令为止。
|
||
- 保证命令格式合法(但不保证命令所执行的内容合法,具体可参见上文)。
|
||
|
||
|
||
### 输出格式
|
||
|
||
按照指令说明部分所要求的格式输出。
|
||
|
||
### 数据范围
|
||
|
||
对于 60% 的数据,队伍总数 $N \le 500$ ,操作次数 $\mathit{opt}\le 10^4$ 。
|
||
|
||
对于 100% 的数据,队伍总数 $N \le 10^4$,题目总数 $M \le 26$ ,比赛时长 $T \le 10^5$ ,操作次数 $\mathit{opt}\le 3\times 10^5$ ,刷新榜单次数 $\mathit{opt_{flush}} \le 1000$ 。封榜次数 $\mathit{opt_{freeze}}\le 10$ 。
|
||
|