Logo ryp 的博客

博客

更新的新 Rating 系统!

avatar ryp 2026-09-05 13:09:34

如题,我们更新到了更先进的 Glicko-2 Rating 系统!

目测现在大家的 Rating 都很合理。

同时,Glicko-2 给我们带来了除了 Rating 外的两个参数:RD 和 Vol。

RD 可以理解为方差,一个人的 Perf 有 95% 的可能在 [Rating - 2RD, Rating + 2RD] 内。

Vol 表示这个人最近表现的波动程度。Vol 大的人,最近表现的特别好,或者特别烂。

我们还添加了一个 Rating 分布图像,在每个人的首页,Rating 曲线下面可以看到。非常有趣。

另外,格言已经回滚。但由于备份文件损坏,回滚到的是 7 月 4 日下午 14:00 的版本。

论文地址:Glicko-2 PDF

代码:

function g2_g($x) {
        return 1 / sqrt(1 + 3 * $x * $x / M_PI / M_PI);
}

function g2_E($u, $uj, $pj) {
        return 1 / (1 + exp(-g2_g($pj) * ($u - $uj)));
}

function g2_f($x, $delta, $phi, $v, $a, $tau) {
        return (exp($x) * ($delta * $delta - $phi * $phi - $v - exp($x))
                / (2 * pow($phi * $phi + $v + exp($x), 2)))
                - ($x - $a) / $tau / $tau;
}

function calcRatingGlicko2($standings, &$new_rating, &$new_rd, &$new_vol) {
        $TAU = 0.6;

        $n = count($standings);
        $rating = array();
        $rd = array();
        $vol = array();
        for ($i = 0; $i < $n; ++$i) {
                $rating[$i] = ($standings[$i][2][1] - 1500) / 173.7178;
                $rd[$i] = $standings[$i][2][2] / 173.7178;
                $vol[$i] = ($standings[$i][2][3] ?? 0.06);
                if ($vol[$i] <= 0) $vol[$i] = 0.06;
        }

        if ($n == 1) {
                $new_rating[0] = $rating[0];
                $new_rd[0] = $rd[0];
                $new_vol[0] = $vol[0];
        }

        for ($i = 0; $i < $n; $i++) {
                $delta = 0;
                $v = 0;

                for ($j = 0; $j < $n; $j++) if ($i != $j) {
                        $s = 0;
                        if ($standings[$i][3] > $standings[$j][3])
                                $s = 0;
                        else if ($standings[$i][3] == $standings[$j][3])
                                $s = 0.5;
                        else
                                $s = 1;
                        $e = g2_E($rating[$i], $rating[$j], $rd[$j]);
                        $v += pow(g2_g($rd[$j]), 2) * $e * (1 - $e);
                        $delta += g2_g($rd[$j]) * ($s - $e);
                }

                if ($v < 1e-20) $v = 1e-20;
                $v = 1 / $v;
                $delta *= $v;

                if ($delta > 10) $delta = 10;
                if ($delta < -10) $delta = -10;

                $a = 2 * log($vol[$i]);

                $A = $a;
                if ($delta * $delta > $rd[$i] * $rd[$i] + $v)
                        $B = log($delta * $delta - $rd[$i] * $rd[$i] - $v);
                else {
                        for ($k = 1; $k < 100 && g2_f($a - $k * $TAU, $delta, $rd[$i], $v, $a, $TAU) < 0; ++$k)
                                ;
                        $B = $a - $k * $TAU;
                }

                $fa = g2_f($A, $delta, $rd[$i], $v, $a, $TAU);
                $fb = g2_f($B, $delta, $rd[$i], $v, $a, $TAU);
                while ($fa * $fb > 0) {
                        $B -= $TAU;
                        $fb = g2_f($B, $delta, $rd[$i], $v, $a, $TAU);
                }

                $k = 0;
                while (abs($B - $A) > 1e-6 && $k < 100) {
                        if (!is_finite($fa) || !is_finite($fb)) break;
                        if (abs($fa - $fb) < 1e-15) break;
                        $C = $A + ($A - $B) * $fa / ($fb - $fa);
                        $fc = g2_f($C, $delta, $rd[$i], $v, $a, $TAU);
                        if ($fc * $fb <= 0) {
                                $A = $B;
                                $fa = $fb;
                        }
                        else
                                $fa = $fa / 2;
                        $B = $C;
                        $fb = $fc;
                        ++$k;
                }

                $new_vol[$i] = exp(($A + $B) / 4);
                $new_rd[$i] = sqrt($rd[$i] * $rd[$i] + $new_vol[$i] * $new_vol[$i]);
                $new_rd[$i] = 1 / sqrt(1 / $v + 1 / $new_rd[$i] / $new_rd[$i]);
                $new_rating[$i] = $rating[$i] + $delta / $v * $new_rd[$i] * $new_rd[$i];
                $new_rating[$i] = $new_rating[$i] * 173.7178 + 1500;
                $new_rd[$i] *= 173.7178;
        }
}

原帖:

WyOJ 现在切换到了新的 和 Codeforces 算法相同的 Rating 系统。

具体算法:

写的很着急,如果有问题可以跟帖。

同时,所有 Rating 已经重新计算。现在的 Rating 应该是非常合理的。

新的 Rating 计算函数,采用 Codeforces 算法。

具体算法是:

设参赛前每人的 Rating 是 $r_i$, 比赛的最终排名是 $R_i$,那么

首先承认:在先前 Rating 的基础上,i 战胜 j 的概率是 $P_ij = \dfrac{1}{1 + 10^{\dfrac{r_j - r_i}{400}}}$

立即得到 i 的期望排名 $E_i = \sum_{j \ne i} P_{ji} + 1$

显然,真正的排名和期望排名之间的差距,就是 rating delta 的来源。

我们取 $S_i = \sqrt{E_i R_i}$,二分出一个 u 使得计算出的 $S_i = E_i$,rating 应当向 u 的方向变化。

取 $\delta = \dfrac{u + r_i}{2}$,随后我们对 $\delta$ 进行微调。

第一次微调:取 $d = \dfrac{-1 - \sum_{i} \delta_i}{n}$,将所有人的 \delta 加上 d。

第二次微调:取排名前 $s = \min{n, 4\sqrt n}$ 高的人,让这些人的 rating 变化量之和为零,得到 d。

随后再将所有人的 $\delta$ 加上 d,得到最终的 rating。

/*
 * 新的 Rating 计算函数,采用 Codeforces 算法。
 * 具体算法是:
 * 设参赛前每人的 Rating 是 r_i, 比赛的最终排名是 R_i,那么
 * 首先承认:在先前 Rating 的基础上,i 战胜 j 的概率是 P_ij = \dfrac{1}{1 + 10^{\dfrac{r_j - r_i}{400}}}
 * 立即得到 i 的期望排名 E_i = \sum_{j \ne i} P_ji + 1
 * 显然,真正的排名和期望排名之间的差距,就是 rating delta 的来源。
 * 我们取 S_i = \sqrt{E_i R_i},二分出一个 u 使得计算出的 S_i = E_i,rating 应当向 u 的方向变化。
 * 取 \delta = \dfrac{u + r_i}{2},随后我们对 \delta 进行微调。
 * 第一次微调:取 d = \dfrac{-1 - \sum_{i} \delta_i}{n},将所有人的 \delta 加上 d。
 * 第二次微调:取排名前 s = \min{n, 4\sqrt n} 高的人,让这些人的 rating 变化量之和为零,得到 d。
 * 随后再将所有人的 \delta 加上 d,得到最终的 rating。
 * */
function calcRatingX($standings) {
        $n = count($standings);
        $rating = array();
        $delta = array();
        $ds = 0;
        for ($i = 0; $i < $n; ++$i)
                $rating[$i] = $standings[$i][2][1];
        for ($i = 0; $i < $n; ++$i) {
                $l = 0;
                $r = 4000;
                while ($l <= $r) {
                        $mid = ($l + $r) / 2;
                        $e = 1;
                        for ($j = 0; $j < $n; ++$j)
                                if ($i != $j)
                                        $e += 1 / (1 + pow(10, ($mid - $rating[$j]) / 400));
                        $s = sqrt($e * ($i + 1));
                        if ($e <= $s)
                                $r = $mid - 1;
                        else
                                $l = $mid + 1;
                }
                $delta[$i] = (($l + $r) / 2 - $rating[$i]) / 2;
                $ds += $delta[$i];
        }
        $ds = (-1 - $ds) / $n;
        for ($i = 0; $i < $n; ++$i)
                $delta[$i] += $ds;
        $s = min($n, 4 * sqrt($n));
        $ds = 0;
        for ($i = 0; $i < $s; ++$i)
                $ds += $delta[$i];
        $ds = -$ds / $s;
        for ($i = 0; $i < $s; ++$i)
                $delta[$i] += $ds;
        $R = array();
        for ($i = 0; $i < $n; ++$i)
                $R[$i] = ceil($rating[$i] + $delta[$i]);
        return $R;
}

评论

wfirstzhang
wfandy,一个B班的,3580 Rating > KSCD
wfirstzhang
显然现在wyoj不是体量比较大的OJ,用期望排名不会太合理..
zxh_qwq
我有一个计算方法,比如你分Div,NOIP模拟赛来讲 rk1 3000perf,rk倒一 1200perf,然后等差数列一下所有人的perf,你每次的rating变动是|rating-perf|/5
WF_wxl
可以每个题设一个杠杆(现在OJ里大多数题都是CF原题,可以直接找评分然后/100之类的),每道题的perf是杠杆*分数/100,最终perf为所有题perf之和。
Gejunyi
你他妈是不是写挂了
WF_wxl
注意到,B班小孩哥3580,而woden298分
wfirstzhang
# 用户名 总分 A 1 _ryp_ 100 0:01:51 100 0:01:51 2 tester1 100 0:03:56 100 0:03:56 3 tester2 100 0:04:11 100 0:04:11 4 tester3 100 0:04:27 100 0:04:27 5 wfirstzhang 0 0:00:00 0
wfirstzhang
ryp还在-10000
Gejunyi
ryp赶紧来干活
wfirstzhang
ryp还在测试
woden
到目前为止,rating曾比我低的同学都已经涨上去了,只有我还是298,成为了wyoj倒一
wfirstzhang
新的rating还不错,但是ryp黑化了
Gejunyi
zc
zxh_qwq
``` function calcRating($standings, $standing , $old_rating , &$new_rating , $max_rating , $min_rating) { $n = count($standings); $rating = array(); $delta = array(); for( $i = 0; $i < $n; ++$i) { $perf = $max_rating - ($max_rating - $min_rating) / n * i; $delta[i] = ($perf - $old_rating) / 5; $sum = $sum + $delta[i]; } for( $i = 0; $i < $n; ++$i) { $delta[i] = $delta[i] + $sum / n; $new_rating = $old_rating + $delta; } } ```
zxh_qwq
```cpp function calcRating($standings, $standing , $old_rating , &$new_rating , $max_rating , $min_rating) { $n = count($standings); $rating = array(); $delta = array(); for( $i = 0; $i < $n; ++$i) { $perf = $max_rating - ($max_rating - $min_rating) / n * i; $delta[i] = ($perf - $old_rating) / 5; $sum = $sum + $delta[i]; } for( $i = 0; $i < $n; ++$i) { $delta[i] = $delta[i] + $sum / n; $new_rating = $old_rating + $delta; } } ```
zxh_qwq
https://webnote.cc/wyztsdqwqwq 这是我写的rating系统,你看最近一场的rating成啥了,+0.03 hyw
zxh_qwq
我说先用着我这个就行,太复杂了不好,
zxh_qwq
毕竟比赛数也不怎么多,别再折腾了,就这样吧
KSCD_
强强

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

ryp Avatar
我拒绝零假设
活跃度 近 53 周
查看全年