首页 > 代码库 > 【综合】天梯算法

【综合】天梯算法

<!--版权声明!转载请说明出处谢谢:)-->

<!--by Polo Shen: http://www.cnblogs.com/polossk/ –>

 

目标

今天听到有人说某学校的ACM队搞了一个Rating System,就是类似TopCoder和Codeforces的Rating一样,直观的积分更容易体现比赛的结果,以及大家的最近表现。而且据说他们把这个作为一个考核标准之一。

听上去好赞的样子!

可是我们学校木有啊!!!

紧接着我就想到了当时看《社交网络》时,扎克伯格搞得那个“点妹子”活动。

程序员木有点新创意简直是在玷污程序员的工作啊!

搞起一个自己的Rating System。

天梯算法简介

还是先恶补知识好了。所谓的天梯算法,其实很简单的,虽然数学证明很复杂。。。天梯算法的基础是建立在人群的水平基本满足正态分布上的。简单地说,就是假设大部分人旗鼓相当,同时存在少部分精英和部分水平较低的人。同时核心的操作也不言而喻,简单地说,同一水平的人比赛,胜者加分,败者减分。差距比较大的两个人比赛,爆冷之后加分减分会高出同水平的但是也不会暴涨。

这里我用的是Elo Rating System.

大致的算法如下:

1、假设这两个人的当前的分数已经给出:

Rating_1

2、计算其获胜的期望:

Rating_2

Rating_3

3、根据实际的结果来进行对分数的修正:

Rating_4

Rating_5

在这里,S表示的是结果,也就是胜平负代表的1, 0.5, 0;K表示的是比赛系数。通常一次比赛分值变动量不会太大。

实际实现

但是,这里和刚才的那个模型不一样。刚才的那个模型是为国际象棋棋手准备的,也就是两个人随意的交手,然后在涨分。换句话说,也许你的比赛场数越多,你的分数也就会更好看。

没听懂?Dota,LOL玩过吧,比赛永远是两个队在互相掐架。

但是Codeforces不一样,可能是上百人上千人同时在玩这个游戏,那么应该怎么搞?

我给的想法是最简单的想法:均摊比赛系数,每个人都参与比较,最后再和自己比较。

什么意思呢?我把一次比赛,假设有n个选手同时参赛,对于其中的选手i,相当于同时和n-1个选手进行了一场同样题目的比赛。所以我将比赛的系数均摊给参赛选手(当然有更好的分配方法,就是根据两者的赛前差距进行分配,这样更为接近现实),分配好之后,只需要让他们两两之间互相“比试”一番,就好了。

但是这样的话,就会造成有的人不停涨分,而且涨分幅度一次比一次大。所以必须引入自我比较法。简单地说,假设一个1700分的人参加一个800人参加的,所有参赛选手的最高分即为1700分的比赛,如果这个人,前一场是全场第2,这场全场第12。虽然他的排名很高,但是他输给了自己的上一场,还是会“下降”一些分数。但是这里的下降并不是绝对意义的下降,是指“涨幅”下降。也就是说,他的分数会基本保持在这个水平,但是不会突然跌下去或者突然涨分。

这就是我的大致思路了。毕竟不是大的项目,简单的做下就可以了。

代码

代码我使用的是Ruby,通过传入一个文本文档,上面记录参赛同学的:

<Rank><Name><Rating><SolvedProblem>

四个数据来进行计算。(其实那个出题数是没有必要的。。。)

最后输出一个文本文档,上面记录同学的

<Rank><Name><NewRating>

# encoding = UTF-8class Student    attr_accessor :name  attr_accessor :rating  attr_accessor :rank  attr_accessor :_solve  attr_accessor :_drating    def initialize(name = "Unknown", rak = 100, rtg = 1500, solve = 0)    @rank = rak.to_i    @name = name    @rating = rtg.to_i    @_drating = 0    @_solve = solve.to_i  end    def initialize(str2)    str = str2.to_s    data = str.split( )    @rank = data[0].to_i    @name = data[1]    @rating = data[2].to_i    @_solve = data[3].to_i    @_drating = 0  end      def update(d) # 累加 rating 改变量    @_drating += d    end      def modify # 更新rating    @rating += @_drating    @_drating = 0        @rating = @rating.to_i  end      def toStringLine        return "#{@rank}\t#{@name}\t#{@rating}\n"    end      def print    #puts "#{@rank}\t#{@name}\t#{@rating}"        puts "#{@rating}"  end  end$Kcef = 60 # 比赛的系数=beginRating算法:针对两个参加比赛的人,互相比较,每次比较都更新一次_drating。比较法则:先比较题数:多者胜,少者负;若平手,比较排名;根据比较结果选取系数进行计算。更新系数法则:将本场的比赛系数按参赛人数分配给各个学生计算方法如下:  cef = $Kcef / sz更新法则:更新时,将两两同学互相比较,然后每次计算出的增量累加到_drating上即可增量计算方法如下:  A: dA = cef * (sA - eA)  B: dB = cef * (sB - eB)最后直接累加即可。=enddef calc_eA_eB(rA, rB)    da = (rB - rA) / 400.0  db = -da  ta = 10.0**da + 1.0  tb = 10.0**db + 1.0  ea = 1.0 / ta  eb = 1.0 / tb  return [ea, eb]enddef calc_sA_sB(a, b)  solveA = a._solve  solveB = b._solve  pA = a.rank  pB = b.rank  if solveA < solveB    return [0.0, 1.0]  elsif solveA > solveB    return [1.0, 0.0]  elsif pA < pB    return [1.0, 0.0]  else    return [0.0, 1.0]  endenddef calc_cef(ra, rb, sz)    return $Kcef / szenddef competition(a, b, sz) # 修改两人的增量    eA, eB = calc_eA_eB(a.rating.to_f, b.rating.to_f)  sA, sB = calc_sA_sB(a, b)    cef = calc_cef(a.rank.to_f, b.rank.to_f, sz.to_f)  dA = (sA - eA) * cef    dB = (sB - eB) * cef    a.update(dA)    b.update(dB)enddef read( path )    items = Array.new    File.open(path, :encoding => utf-8).each_line do |line|        item = Student.new(line)        items << item    end    sz = items.size    # 逐一比较    for i in 0...sz        for j in 0...sz            if i == j then              next            else               competition(items[i], items[j], sz.to_i);            end         end    end    # 自身比较    items.each do |data|        temp = data        temp.modify # 更新这场的rating        competition(temp, data, sz)        temp.modify # 自我比较后的rating        data.modify # 对 rating 取平均值        data.rating = ((temp.rating.to_i + data.rating.to_i ) / 2).to_i    end        file = File.new("result.txt", "w+")    items.each do |data|        data.modify        file.print data.toStringLine    endendread("contest1.txt")
View Code