UVa 10380 Shogi Tournament 这道题,是我见过把“比赛排名规则”考得最细的一道老题。题目背景是将棋联赛,输入给的是选手之间的胜负矩阵,要求输出最终排名。听起来就是读矩阵、算积分、排个序,但真正动手写的时候,破同分规则和排序比较器这两关会卡掉一大半人。我当年第一次交就是 WA,后来才发现不是算法错了,而是我把 SB 系数算错了对象。这篇文章把整道题从规则拆解到 AC 代码完整梳理一遍,适合正在刷 UVa 基础题、想搞懂比赛排名计算,或者准备面试时被问排序稳定性的读者。
1. 将棋联赛的排名规则:题目到底想让你算什么
1.1 输入矩阵是在描述一个怎样的赛果
将棋是日本象棋,联赛形式就是单循环(round-robin):每两名选手之间都交手。这个题的输入很简洁:第一行是选手数 n,接下来 n 行是选手名字,再接下来是一个 n×n 的矩阵。矩阵第 i 行第 j 列的数值,表示第 i 个选手赢第 j 个选手的场数,主对角线是 '-'。
这句话多读两遍,你就明白为什么很多人会在读入上出错:矩阵行之间不是独立的,win[i][j] 和 win[j][i] 是一场比赛的两面。如果把 '-' 当成 0,等于给每个选手算了一场“自己赢自己”,积分会整体虚高,而且每个选手虚高的幅度还不一样,排名直接就乱了。
另外要注意,这个矩阵给出的是“赢了几场”,不是“赢没赢”。如果每对选手只下一场,那么 win[i][j] 只能是 0 或 1,win[i][j] + win[j][i] = 1;如果下三场(很多将棋联赛确实是三番棋),那么 win[i][j] 可以到 2 甚至 3。代码里最好按“场数”来处理,而不是按“是否战胜过”来处理,后面算 SB 的时候这个区别会放大。
1.2 胜负积分:为什么可以简化为“数胜场”
题目的计分规则非常直白:赢一场得 1 分,输一场得 0 分。既然每个选手打的比赛场数相同,那么积分就等于胜场数,也等价于“胜场数 - 负场数 + 常数”。这个等价关系很重要,因为有些选手矩阵里会出现多场对局,用胜场数最不容易算错。
如果题目里存在和棋(将棋里的千日手可以判和),规则可能会是胜 1 分、和 0.5 分、负 0 分。这个变体会引出浮点精度问题,我放在第 2 章专门说,先把无和棋的情况想清楚。
1.3 Sonneborn-Berger 系数:战胜强者的含金量
积分相同怎么排?很多比赛先看直接交锋,但直接交锋有天然缺陷:它只适用于两人并列,三人以上形成循环(A 赢 B,B 赢 C,C 赢 A)时就完全失效。Shogi Tournament 用的是 Sonneborn-Berger 系数,简称 SB。规则是:把某个选手赢过的每一场对手的最终积分全部加起来,作为他的 SB 值;积分高的排前面,积分相同的比 SB,SB 高的排前面。
这个规则的本质是:同样是赢一场,你赢的是积分榜首,还是积分垫底,含金量完全不一样。SB 系数把“赢谁”的信息编码进了排名依据里,比单纯看胜场公平得多。这个思路在真实棋类比赛的排名软件里非常常见,理解它之后,你会发现很多排行榜算法都是同一个套路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从规则到算法:SB 为什么不用迭代就能算出来
2.1 计算顺序:先积分,后 SB,顺序不能反
SB 的定义里用的是“对手的最终积分”,而“最终积分”只取决于胜负矩阵,不取决于 SB 本身。这就构成了一个清晰的前置依赖:先扫一遍矩阵把所有选手的积分算出来,再扫一遍矩阵,把每一场胜利对应的对手积分累加进 SB。
很多第一次写这道题的人会踩一个逻辑坑:以为 SB 会反过来影响积分,于是加
