题目分析
本题要求模拟欧洲足球锦标赛的排名规则。给定若干场足球比赛的结果,需要根据特定规则对所有参赛队伍进行排名。规则如下:
-
积分规则
- 胜:333 分
- 平:111 分
- 负:000 分
-
排名优先级(依次比较,若相同则进入下一项)
- 总积分
- 净胜球(进球数 −-− 失球数)
- 胜场数
- 进球数
-
并列处理
如果多支队伍在上述四项后仍然并列,则进入子联赛模式:- 仅考虑这些队伍之间的比赛结果,重新计算积分、净胜球、胜场数、进球数。
- 如果此时积分不再相同,则按照新的积分进行分组,并递归地对每个分组继续执行上述过程。
- 如果积分仍然全部相同(即子联赛内所有队伍积分仍一致),则直接使用子联赛的四项数据进行排名(依然按照上述优先级)。
- 如果四项数据仍然完全相同,则这些队伍并列同一名次,并按队名字母顺序输出。
-
输入输出说明
- 输入包含多个测试用例,每个用例先给出比赛场数 GGG,随后 GGG 行每行格式为:
TEAM1 TEAM2 GOALS1 GOALS2\texttt{TEAM1 TEAM2 GOALS1 GOALS2}TEAM1 TEAM2 GOALS1 GOALS2 - 队伍名称仅由小写字母组成,长度不超过 202020。
- 需输出所有至少参加一场比赛的队伍的排名,格式为 PLACE TEAM\texttt{PLACE TEAM}PLACE TEAM。
- 测试用例之间用空行分隔。
- 输入包含多个测试用例,每个用例先给出比赛场数 GGG,随后 GGG 行每行格式为:
解题思路
本题的关键在于实现递归分组排名的逻辑。整体流程如下:
1. 数据结构设计
Team结构体:保存队伍的积分、进球、失球、胜场,并提供净胜球计算方法。Game结构体:保存一场比赛的两支队伍 ID 和各自进球数。- 使用
map<string, int>建立队名到 ID 的映射,便于处理。
2. 全局数据与子联赛计算
- 首先读入所有比赛,更新全局的
Team数据(即所有比赛都计入)。 - 在需要处理并列时,调用
calcLeague()函数,仅计算指定队伍集合内部的比赛数据,返回一个新的Team数组。
3. 递归排名函数 rankTeams()
- 输入:一组队伍的 ID 列表。
- 过程:
- 计算这些队伍在子联赛中的数据。
- 按积分进行分组。
- 对于每个分组:
- 如果只有一支队伍,直接输出。
- 如果有多支队伍,检查它们在子联赛中积分是否仍然相同:
- 若不同,递归调用
rankTeams()对该分组进一步排名。 - 若相同,则按子联赛的四项数据排序,若完全并列则按字母顺序输出。
- 若不同,递归调用
4. 主流程
- 读入所有比赛,构建全局排名(按四项数据排序)。
- 按积分分组,对每个积分相同的组调用
rankTeams()进行细致排名。 - 合并所有分组,输出最终名次。
5. 时间复杂度
- 队伍数 N≤10000N \leq 10000N≤10000,比赛数 G≤10000G \leq 10000G≤10000。
- 每次
calcLeague()需要遍历所有比赛,最坏情况下递归深度可能达到 NNN,但实际并列情况不会太深,且 GGG 不大,可以在时间限制内通过。
代码实现
// Euro 2004
// UVa ID: 917
// Verdict: Accepted
// Submission Date: 2025-10-18
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 常量定义:最大队伍数量和最大比赛数量
const int MAX_TEAMS = 10010, MAX_GAMES = 10010;
// 队伍数据结构:存储队伍的积分、进球数、失球数和胜场数
struct Team {
int pts = 0, gs = 0, gc = 0, win = 0; // 积分、进球数、失球数、胜场数
// 计算净胜球
int gd() const { return gs - gc; }
};
// 比赛数据结构:存储两支队伍的ID和各自的进球数
struct Game {
int t1, t2, g1, g2; // 队伍1ID, 队伍2ID, 队伍1进球, 队伍2进球
};
// 全局变量声明
Game games[MAX_GAMES]; // 存储所有比赛的数组
int gameCnt, teamCnt; // 比赛数量和队伍数量计数器
Team global[MAX_TEAMS]; // 存储所有队伍全局数据的数组
map<string, int> nameToId; // 队名到ID的映射
string idToName[MAX_TEAMS]; // ID到队名的反向映射
// 队伍比较函数:用于排序,按积分、净胜球、胜场数、进球数、字母顺序的优先级比较
bool teamCompare(const Team& a, const Team& b) {
if (a.pts != b.pts) return a.pts > b.pts; // 积分高的排前面
if (a.gd() != b.gd()) return a.gd() > b.gd(); // 净胜球多的排前面
if (a.win != b.win) return a.win > b.win; // 胜场数多的排前面
if (a.gs != b.gs) return a.gs > b.gs; // 进球数多的排前面
return false; // 其他情况保持原顺序
}
// 判断两个队伍在所有排名参数上是否完全相等
bool teamsEqual(const Team& a, const Team& b) {
return a.pts == b.pts && a.gd() == b.gd() && a.win == b.win && a.gs == b.gs;
}
// 计算子联赛数据:只考虑指定队伍之间的比赛
void calcLeague(const vector<int>& teams, Team res[]) {
// 初始化结果数组
for (int i = 0; i < teamCnt; i++) res[i] = Team();
// 标记哪些队伍在当前子联赛中
bool inLeague[MAX_TEAMS] = {false};
for (int t : teams) inLeague[t] = true;
// 遍历所有比赛,只处理子联赛内队伍之间的比赛
for (int i = 0; i < gameCnt; i++) {
const Game& g = games[i];
if (inLeague[g.t1] && inLeague[g.t2]) {
Team& t1 = res[g.t1], & t2 = res[g.t2];
// 更新进球和失球数据
t1.gs += g.g1; t1.gc += g.g2;
t2.gs += g.g2; t2.gc += g.g1;
// 根据比赛结果更新积分和胜场数
if (g.g1 > g.g2) { t1.pts += 3; t1.win++; } // 队伍1获胜
else if (g.g1 < g.g2) { t2.pts += 3; t2.win++; } // 队伍2获胜
else { t1.pts++; t2.pts++; } // 平局
}
}
}
// 递归排名函数:对给定队伍组进行排名,处理并列情况
vector<vector<int>> rankTeams(const vector<int>& teams) {
// 基本情况:如果只有一支队伍,直接返回
if (teams.size() == 1) return {teams};
// 计算当前组的子联赛数据
Team sub[MAX_TEAMS];
calcLeague(teams, sub);
// 将队伍数据与ID配对并排序
vector<pair<Team, int>> vec;
for (int t : teams) vec.push_back({sub[t], t});
sort(vec.begin(), vec.end(), [](const pair<Team, int>& a, const pair<Team, int>& b) { return teamCompare(a.first, b.first); });
// 按积分分组
vector<vector<int>> res;
for (size_t i = 0; i < vec.size(); ) {
vector<int> group = {vec[i].second};
int curPts = vec[i].first.pts;
// 将积分相同的队伍分到同一组
while (++i < vec.size() && vec[i].first.pts == curPts) group.push_back(vec[i].second);
if (group.size() == 1)
res.push_back(group); // 单支队伍直接加入结果
else {
// 对积分相同的队伍组进行进一步处理
Team finalSub[MAX_TEAMS];
calcLeague(group, finalSub);
// 检查在子联赛中这些队伍的积分是否仍然相同
bool samePts = true;
int firstPts = finalSub[group[0]].pts;
for (size_t j = 1; j < group.size() && samePts; j++)
if (finalSub[group[j]].pts != firstPts) samePts = false;
if (!samePts) {
// 积分不再相同,递归处理这个组
auto subGroups = rankTeams(group);
res.insert(res.end(), subGroups.begin(), subGroups.end());
} else {
// 积分仍然相同,使用其他排名标准
vector<pair<Team, int>> finalVec;
for (int t : group) finalVec.push_back({finalSub[t], t});
sort(finalVec.begin(), finalVec.end(), [](const pair<Team, int>& a, const pair<Team, int>& b) { return teamCompare(a.first, b.first); });
// 按排名参数分组,处理完全并列的情况
for (size_t k = 0; k < finalVec.size(); ) {
vector<int> equalTeams = {finalVec[k].second};
size_t m = k + 1;
// 找到所有参数完全相同的队伍
while (m < finalVec.size() && teamsEqual(finalVec[k].first, finalVec[m].first)) {
equalTeams.push_back(finalVec[m].second); m++;
}
// 完全并列的队伍按字母顺序排序
sort(equalTeams.begin(), equalTeams.end(), [](int a, int b) { return idToName[a] < idToName[b]; });
res.push_back(equalTeams);
k = m;
}
}
}
}
return res;
}
// 获取队伍ID:将队名映射为整数ID,如果不存在则创建新ID
int getId(const string& name) {
if (nameToId.find(name) == nameToId.end()) {
int id = teamCnt++;
nameToId[name] = id;
idToName[id] = name;
return id;
}
return nameToId[name];
}
int main() {
// I/O优化
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
int T; // 测试用例数量
cin >> T;
// 处理每个测试用例
for (int tc = 0; tc < T; tc++) {
if (tc > 0) cout << '\n'; // 测试用例之间输出空行
// 初始化全局变量
gameCnt = 0, teamCnt = 0;
nameToId.clear();
int G; // 比赛数量
cin >> G;
// 初始化全局队伍数据
for (int i = 0; i < MAX_TEAMS; i++) global[i] = Team();
// 读取并处理所有比赛数据
for (int i = 0; i < G; i++) {
string t1, t2;
int g1, g2;
cin >> t1 >> t2 >> g1 >> g2;
// 获取队伍ID
int id1 = getId(t1), id2 = getId(t2);
// 存储比赛信息
games[gameCnt++] = {id1, id2, g1, g2};
// 更新全局队伍数据
Team& tm1 = global[id1], & tm2 = global[id2];
tm1.gs += g1; tm1.gc += g2; // 更新队伍1的进球失球
tm2.gs += g2; tm2.gc += g1; // 更新队伍2的进球失球
// 根据比赛结果更新积分和胜场
if (g1 > g2) { tm1.pts += 3; tm1.win++; } // 队伍1获胜
else if (g1 < g2) { tm2.pts += 3; tm2.win++; } // 队伍2获胜
else { tm1.pts++; tm2.pts++; } // 平局
}
// 收集所有队伍的ID
vector<int> allTeams;
for (int i = 0; i < teamCnt; i++) allTeams.push_back(i);
// 按全局数据对队伍进行初步排序
sort(allTeams.begin(), allTeams.end(), [](int a, int b) {
return teamCompare(global[a], global[b]);
});
// 构建最终排名分组
vector<vector<int>> finalGroups;
for (size_t i = 0; i < allTeams.size(); ) {
vector<int> group = {allTeams[i]};
int curPts = global[allTeams[i]].pts;
// 按积分分组
while (++i < allTeams.size() && global[allTeams[i]].pts == curPts) group.push_back(allTeams[i]);
if (group.size() == 1) finalGroups.push_back(group); // 单支队伍直接加入
else {
// 多支队伍积分相同,进入子联赛排名
auto subGroups = rankTeams(group);
finalGroups.insert(finalGroups.end(), subGroups.begin(), subGroups.end());
}
}
// 输出最终排名结果
int place = 1;
for (const auto& group : finalGroups) {
for (int t : group) cout << place << ' ' << idToName[t] << '\n';
place += group.size(); // 更新名次
}
}
return 0;
}
总结
本题是一个典型的模拟+递归分组排名问题,关键在于理解题目中“子联赛”的递归处理逻辑。实现时需要注意:
- 正确处理全局数据与子联赛数据的分离计算。
- 递归函数中需判断“积分是否仍然相同”以决定是否继续分组。
- 完全并列时按字母顺序输出。
- 使用
map处理队名与 ID\texttt{ID}ID 的映射,提高代码可读性与效率。
该算法虽然在最坏情况下可能达到 O(N⋅G)O(N \cdot G)O(N⋅G) 的复杂度,但由于数据范围不大(N,G≤10000N, G \leq 10000N,G≤10000),且实际并列情况有限,完全可以在规定时间内通过。


被折叠的 条评论
为什么被折叠?



