【项目】关于 CodeForces 的 EloRating 机制的研究

关于 CodeForces 的 EloRating 机制的研究

作者:@cjh20090318

摘要

对 CodeForces EloRating 机制的研究,开发 CJH Rating System,以及介绍它的使用方法和更新日志。

关键词

Elo Rating。

引言

注意到同学们对自己的分数/考试成绩很有积极性,经常喜欢比较,但是总是拿自己最好的几次来比较。

为了更好的体现同学们的水平,促进各位同学对学习的积极性,我开发了一款仿照 CodeForces EloRating 计算系统的 CJH Rating System(以下简称 CRS)。

正文

使用方法

先空着,来不及了,可以先看看更新日志

更新日志

v(1.0) 更新于 2023/1/28(由于历史久远且存在 Bug 未留存该版本代码和程序)

Update
  • 创建 Main.exe

  • 支持从同文件夹下的 Users.txt 导入用户列表。

  • 支持通过 Register 命令注册用户,所有用户初始 Rating 值均为 1500

  • 支持通过 Contest 命令从同文件夹下的 Contest.txt 中导入比赛排名表,同时可选择 Unrated 或 Rated,读取到新用户时可自动注册。

  • 支持通过 Standing 命令在控制台输出排名(仅有 Rank,Name,Rating)。

Bug
  • 最多仅支持注册 1000 名用户,一个用户名可以被多次注册。

  • 所有修改仅在程序中有效,无法保存。

  • 当比赛次数过多时整体 Rating 会下降并逐渐变为负 Rating。

  • 控制台排名可读性较差。

v(1.1) 更新于 2023/1/29(由于历史久远且存在 Bug 未留存该版本代码和程序)

Update
  • 更新 Rating 的计算 Bug。设置“奖励分”。主要是因为比赛次数超过 87 次后就会下降,所以每场比赛的 Rating 的附加值为 比赛次数86
Bug
  • 除 Rating 更新以外其他问题仍然存在。

v(1.2) 更新于 2023/1/30(由于历史久远未留存该版本代码和程序)

Update
  • 增加了保存 Max Rating。

  • 增加 Register 命令对于多次注册同一用户名的限制。

  • 支持通过 Save 命令保存状态了!(保存地址在同文件夹下的 Users.txt 中)

  • 支持通过 Delete 命令删除用户。

  • 支持通过 Ask 命令查询某一个用户的信息(添加了名字高亮和称号,命名规则类似于 CodeForces;输出到同文件夹下的 /Users/%username%.html 中,导出后自动打开)。

  • 在程序第一次使用开始时添加“设置管理员密码”,设置密码后将以 Hash 加密形式加密为三个 unsigned long long 值导出至同文件夹下的 password.txt。如果想要重置密码可以使用 ResetPassword 命令。

Bug
  • 除未解决以外的所有 Bug。
  • Delete 命令删除用户后无法再次进行相同用户名的注册,导入比赛时会发生未知错误。

v(1.3) 更新于 2023/1/31(由于历史久远未留存该版本代码和程序)

Update
  • 更改 Standing 命令,将原来的“控制台输出排名”改为了“导出 HTML 排名”并自动打开。现在的排名有 “Rank,Username,Rating,Max Rating,Competition Times” 栏目。同时排名显示的用户从 All 变为了 Only rated。
Bug
  • 未解决的所有 Bug。

v(1.3) 更新于 2023/2/1

Update
  • 删除后的用户可再次注册,但是信息会被全部删除。
  • 人数支持 100000

v(1.4) (开发中)

Update
  • 可以保存比赛历史记录及排名。
  • 在用户的个人信息下显示他参加过的比赛的信息。(类似于 CodeForces)

结论

该程序还未经过大量测试,未知正确性和性能。欢迎各位体验。

有意的大佬可以私信我获取源码,但是版权仍然归属于 cjh20090318(写 300 行,12 KB 真是不容易)。

下载链接(不含源码)

参考文献

rui_er:CodeForces & AtCoder rating 规则简述 - rui_er 的博客 - 洛谷博客

Mike Mirzayanov:Submission #13861109 - Codeforces

XDFZ 初二物理寒假作业:2、科技论文写法指导

直接开源源码

Main.cpp

/*
 * the code is from chenjh
 * Copyright by Chen Jinhui(cjh20090318,c1120241702) 
*/
//https://codeforces.com/contest/1/submission/13861109
//当比赛次数达到87次时rating会降。 

#include<bits/stdc++.h>
#define MAXN 100001
using namespace std;
typedef double db;
int UserNumber;
struct User{
    string username;
    db rating,MaxRating;
    int tm;
}u[MAXN],tmpu[MAXN];
bool cmp_User(User x,User y){
    return x.rating>y.rating||(x.rating==y.rating&&x.username<y.username);
}
int Ctn;//Contestant Number.
struct Contest{
    string username;
    db score;
    db rnk;
    db rating;
    double delta;
    double seed;
    Contest(){}
    Contest(string _username,int _score,int _rnk,int _rating,int _delta,int _seed){username=_username,score=_score,rnk=_rnk,rating=_rating,delta=_delta,seed=_seed;}
}c[MAXN];
bool cmp_Contest_Point(Contest x,Contest y){
    return x.score>y.score||(x.score==y.score&&x.username<y.username);
}
bool cmp_Contest_Rating(Contest x,Contest y){
    return x.rating>y.rating||(x.rating==y.rating&&x.username<y.username);
}
int CHN;//Contest History Number.
struct ContestHistory{
    string ContestName,ContestDate;
    vector<Contest> Ct;
}CH[MAXN];
map<string,int> UserMap;

//=== Import Users 导入用户 ===// 

void Import(){
    ifstream fin("Users.txt");
    fin>>UserNumber;
    for(int i=1;i<=UserNumber;i++){
        fin>>u[i].username>>u[i].rating>>u[i].MaxRating>>u[i].tm,
        UserMap[u[i].username]=i;
    }
    fin.close();
    ifstream fn("Contests.txt");
    fn>>CHN;
    for(int i=1;i<=CHN;i++){
        getline(fin,CH[i].ContestName);getline(fin,CH[i].ContestDate);
        Contest tmp;
        fn>>tmp.username>>tmp.score>>tmp.rating>>tmp.delta;
        CH[i].Ct.push_back(tmp);
    }
    fn.close();
}

//=== Register 注册用户 ===// 

void Register(string username){
    if(UserMap.find(username)!=UserMap.end()){
        puts("此用户存在,注册失败。");
        return;
    }
    u[++UserNumber]={username,1500,0,0};
    UserMap[username]=UserNumber;
}

//=== Contest  比赛相关 ===//

void ImportContest(string&CN,string&Dt){
    ifstream fin("Contest.txt");
    getline(fin,CN);getline(fin,Dt);
    fin>>Ctn;
    for(int i=1;i<=Ctn;i++) c[i]=Contest("",0,0,0,0,0);
    for(int i=1;i<=Ctn;i++){
        fin>>c[i].username>>c[i].score;
        if(UserMap.find(c[i].username)==UserMap.end()) Register(c[i].username);
    }
    fin.close();
}
double m[MAXN];
double getSeed(int rating){
    /*
        private static double getEloWinProbability(double ra, double rb) {
        return 1.0 / (1 + Math.pow(10, (rb - ra) / 400.0));
        private double getEloWinProbability(Contestant a, Contestant b) {
        return getEloWinProbability(a.rating, b.rating);
        }
    }
    */
    double result = 1;
        for (int i=1;i<=Ctn;i++) {

            result += 1.0/(1.0+pow(10,(c[i].rating-rating)/400.0));
        }

        return result;
}
void UpdateRating(const string CN,const string Dt){
    sort(c+1,c+Ctn+1,cmp_Contest_Point);
    for(int i=1;i<=Ctn;i++)
        c[i].rating=u[UserMap[c[i].username]].rating,
        c[i].rnk=Ctn-i+1,
        c[i].delta=0;
//      cout<<c[i].rnk<<' '<<c[i].username<<endl;
    for(int i=1;i<=Ctn;i++){
        c[i].seed=1;
        for(int j=1;j<=Ctn;j++){
            if(i==j) continue;
            c[i].seed+=1.0/(1.0+pow(10,(c[j].rating-c[i].rating)/400.0));
        }
    }
    for(int i=1;i<=Ctn;i++){
        m[i]=sqrt(c[i].seed*c[i].rnk);
        int left = 1;
        int right = 8000;
        for(;left<=right;left++)if(getSeed(left)>=m[i])break;
//      printf("%d %d\n",i,Ctn);
        /*
        while (right - left > 1) {
            int mid = (left + right) >>1;
            if (getSeed(mid) < m[i]) right = mid;
            else left = mid;
//          printf("%lf %d %d %d\n",getSeed(mid),left,mid,right);
        }
        */
        c[i].delta=(left-c[i].rating)/2;
//        printf("%lf %d %d\n",m[i],left,c[i].delta);
    }
    sort(c+1,c+Ctn+1,cmp_Contest_Rating);
    db sum = 0;
    for (int i=1;i<=Ctn;i++){
        sum += c[i].delta;
    }
    db inc = -sum / Ctn - 1;
    for (int i=1;i<=Ctn;i++) {
        c[i].delta += inc;
    }
    db zeroSumCount = min((int) (4 * round(Ctn)),Ctn);
    for (int i = 1; i <= zeroSumCount; i++) {
        sum += c[i].delta;
    }
    inc = min(max(-sum / zeroSumCount, -10.0), 0.0);
    for (int i=1;i<=Ctn;i++) {
        c[i].delta += inc;
    }
    ++CHN;
    CH[CHN].ContestName=CN,CH[CHN].ContestDate=Dt;
    sort(c+1,c+Ctn+1,cmp_Contest_Point);
//  for(int i=1;i<=Ctn;i++) printf("%d %s %d %d %.0lf\n",c[i].rnk,c[i].username.c_str(),c[i].rating,c[i].delta,c[i].seed);
    for(int i=1,x;i<=Ctn;i++){
        x=UserMap[c[i].username],
        c[i].delta+=(u[x].tm++)/86.0,
        u[x].rating=c[i].rating+c[i].delta,
        u[x].MaxRating=u[x].tm<=1?u[x].rating:max(u[x].MaxRating,u[x].rating);
        CH[CHN].Ct.push_back(c[i]);
    }
}

//=== Ask 查询个人 ===// 
string col[11]={"000000","808080","008000","03A89E","0000FF","AA00AA","FF8C00","FF8C00","FF0000","FF0000","FF0000"};
string tt[11]={"Unrated","Newbie","Pupil","Specialist","Expert","Candidate Master","Master","International Master","Grandmaster","International Grandmaster","Legendary Grandmaster"};
int getTitleNumber(int r){
    if(r<1200) return 1;
    if(r<1400) return 2;
    if(r<1600) return 3;
    if(r<1900) return 4;
    if(r<2100) return 5;
    if(r<2300) return 6;
    if(r<2400) return 7;
    if(r<2600) return 8;
    if(r<3000) return 9;
    return 10;
}
int getTitleNumber(string username){
    int x=UserMap[username],r=u[x].rating;
    if(!u[x].tm) return 0;
    return getTitleNumber(r);
}

//=== Delete 删除用户 ===//
void Delete(string username){
    if(UserMap.find(username)==UserMap.end()){
        puts("查无此户,删除失败。"); 
        return;
    }
    int uid=UserMap[username];
    if(uid==UserNumber) --UserNumber;
    else UserMap[u[UserNumber].username]=uid,swap(u[uid],u[UserNumber--]);
    UserMap.erase(username); 
    puts("删除成功!");
}

//=== Save 保存 ===//
void Save(){
    ofstream fout("Users.txt");
    fout<<UserNumber<<endl;
    for(int i=1;i<=UserNumber;i++){
        fout<<u[i].username<<' '<<(int)u[i].rating<<' '<<(int)u[i].MaxRating<<' '<<u[i].tm<<endl;
    }
    fout.close();
    ofstream fot("Contests.txt");
    fot<<CHN<<endl;
    for(int i=1;i<=CHN;i++){
        fot<<CH[i].ContestName<<endl<<CH[i].ContestDate<<endl;
        for(Contest x:CH[i].Ct){
            fot<<x.username<<endl<<(int)x.score<<' '<<(int)x.rating<<' '<<(int)x.delta<<endl;
        }
        fot<<endl;
    }
    fot.close();
}

//=== password 密码 ===///
const unsigned long long prime[3]={103,10007,998244353};
unsigned long long password[3];
unsigned long long EncodePassword(char s[10000],const int p){
    int l=strlen(s+1);
    unsigned long long ret=0;
    for(int i=1;i<=l;i++) ret=ret*p+s[i];
    return ret;
}
bool PasswordAC(char s[10000]){
    for(int i=0;i<3;i++)
        if(EncodePassword(s,prime[i])!=password[i])
            return 0;
    return 1;
}
int main(){
    puts("Welcome to CJH Rating System!");
    FILE* f=fopen("password.txt","r");
    bool ff=f;
    fclose(f);
    if(!ff){
        puts("请设置管理员密码(只包含可见字符):");
        char userpassword[10000];
        cin>>(userpassword+1);
        for(int i=0;i<3;i++) password[i]=EncodePassword(userpassword,prime[i]);
        puts("设置成功!");
        ofstream fout("password.txt");
        fout<<password[0]<<' '<<password[1]<<' '<<password[2]<<endl;
        fout.close();
    }
    else{
        ifstream fin("password.txt");
        for(int i=0;i<3;i++) fin>>password[i];
        fin.close();
    }
    Import();
    string op;
    while(cin>>op){
        if(op=="Register"){
            string username;
            printf("请输入用户名:");
            cin>>username;
            Register(username); 
        }
        else if(op=="Contest"){
            string CN,Dt;
            ImportContest(CN,Dt);
            puts("正在导入……"); 
            printf("请问是rated还是unrated(Y/N):");
            string rtd;
            cin>>rtd;while(rtd!="Y" && rtd!="N")printf("输入不合法,请重新输入:"),cin>>rtd;
            if(rtd=="Y") UpdateRating(CN,Dt);
        }
        else if(op=="Standing"){
            int RatedNumber=0;
            for(int i=1;i<=UserNumber;i++)if(u[i].tm)tmpu[++RatedNumber]=u[i];
            sort(tmpu+1,tmpu+RatedNumber+1,cmp_User);
            /*
            puts("Rank   Username       Rating");
            for(int i=1;i<=UserNumber;i++) printf("%d   %s         %.0lf\n",i,tmpu[i].username.c_str(),tmpu[i].rating);
            */
            ofstream fout("Standing.html");
            fout<<"<html>\n<head><title>Rating Standing</title></head>\n";
            fout<<"<body>\n";
            fout<<"<h1> CJH Rating Standing </h1><br>\n";
            fout<<"<p style=\"font-size: small;\">Only rated.</p>\n";
            fout<<"<tbody>\n<p>\n<table cellpadding=\"1\" style=\"border-style: solid;\">\n<tr><th style=\"border-style: none solid solid none; border-width: 3px 3px; border-color: #000;\" scope=\"col\">Rank</th>\n<th style=\"border-style: none solid solid none; border-width: 3px 3px; border-color: #000;\" scope=\"col\">Username</th><th style=\"border-style: none solid solid none; border-width: 3px 2px; border-color: #000;\" scope=\"col\">Rating</th><th style=\"border-style: none solid solid none; border-width: 3px 2px; border-color: #000;\" scope=\"col\">Max Rating</th><th style=\"border-style: none solid solid none; border-width: 3px 2px; border-color: #000;\" scope=\"col\">Competition Times</th></tr>\n";
            for(int i=1;i<=RatedNumber;++i){
                fout<<"<tr><td style=\"border-style: none solid solid none; border-width: 1px 3px; border-color: #ccc;\">"<<i<<"</td>\n";
                fout<<"<td style=\"border-style: none solid solid none; border-width: 1px 3px; border-color: #ccc;\">";
                int tn=getTitleNumber((int)tmpu[i].rating);
                if(tn==10){
                    if(tmpu[i].username[0]>0) fout<<""<<tmpu[i].username[0]<<"<font color=#"<<col[tn]<<">"<<tmpu[i].username.substr(1,tmpu[i].username.length()-1)<<"</font>\n";
                    else  fout<<""<<tmpu[i].username.substr(0,2)<<"<font color=#"<<col[tn]<<">"<<tmpu[i].username.substr(2,tmpu[i].username.length()-2)<<"</font>\n";
                }
                else fout<<"<font color=#"<<col[tn]<<">"<<tmpu[i].username<<"</font>\n";
                fout<<"</td>\n";
                fout<<"<td style=\"border-style: none solid solid none; border-width: 1px 3px; border-color: #ccc;\">";
                fout<<"<font color=#"<<col[tn]<<">"<<(int)tmpu[i].rating<<"</font>\n";
                fout<<"</td>\n";
                fout<<"<td style=\"border-style: none solid solid none; border-width: 1px 3px; border-color: #ccc;\">";
                int mtn=getTitleNumber((int)tmpu[i].MaxRating);
                fout<<"<font color=#"<<col[mtn]<<">"<<(int)tmpu[i].MaxRating<<"</font>\n";
                fout<<"</td>\n";
                fout<<"<td style=\"border-style: none solid solid none; border-width: 1px 3px; border-color: #ccc;\">"<<tmpu[i].tm<<"</td>\n</tr>";
            }
            fout<<"</tbody></table></p>\n";
            fout<<"</body></html>\n";
            fout.close();
            system("start Standing.html"); 
        }
        else if(op=="Ask"){
            printf("请输入需要查找的用户名:");
            string username;
            cin>>username;
            while(UserMap.find(username)==UserMap.end())printf("用户不存在,请重新输入:"),cin>>username;
            string FOUT="Users/"+username+".html";
            ofstream fout(FOUT.c_str());
            int tn=getTitleNumber(username);
            fout<<"<html>\n<head><title>"<<username<<" - User CJH Rating</title></head>\n";
            fout<<"<body>\n";
            if(!tn){
                fout<<"<font size=3>Unrated</font><br>\n";
                fout<<"<h1>"<<username<<"</h1><br>\n";
            }
            else{
                fout<<"<strong><font size=3 color=#"<<col[tn]<<">"<<tt[tn]<<"</font></strong><br>\n";
                if(tn==10){
                    if(username[0]>0) fout<<"<h1>"<<username[0]<<"<font color=#"<<col[tn]<<">"<<username.substr(1,username.length()-1)<<"</font></h1><br>\n";
                    else  fout<<"<h1>"<<username.substr(0,2)<<"<font color=#"<<col[tn]<<">"<<username.substr(2,username.length()-2)<<"</font></h1><br>\n";
                }
                else fout<<"<h1><font color=#"<<col[tn]<<">"<<username<<"</font></h1><br>\n";
                int x=UserMap[username],mtn=getTitleNumber((int)u[x].MaxRating);
                fout<<"<img style=\"vertical-align:middle;margin-right:0.5em;\" src=\"https://codeforces.org/s/71520/images/icons/rating-24x24.png\" alt=\"User\'\'s contest rating in FZOJ community\" title=\"User\'\'s contest rating in FZOJ community\"> Contest Rating: "<<"<strong><font color=#"<<col[tn]<<">"<<(int)u[x].rating<<"</font></strong> (max,"<<"<strong><font color=#"<<col[mtn]<<">"<<tt[mtn]<<","<<(int)u[x].MaxRating<<"</font></strong>)<br>\n";
                fout<<"</body>\n";
            }
            fout<<"</html>\n";
            fout.close();
            FOUT="start "+FOUT;
            system(FOUT.c_str());
        }
        else if(op=="Delete"){
            printf("请输入需要删除的用户名:");
            string username;
            cin>>username;
            Delete(username);
        }
        else if(op=="Save"){
            Save();
            puts("保存成功!");
        }
        else if(op=="ResetPassword"){
            char s[10000],s1[10000],s2[10000];
            printf("当前密码:");cin>>(s+1);
            int cnt;
            for(cnt=0;!PasswordAC(s) && cnt<3;cnt++){printf("密码错误!请重试:");cin>>(s+1);}
            if(!PasswordAC(s) && cnt>=3){puts("错误次数过多,自动退出。");continue;}
            printf("新密码:");cin>>(s1+1);
            printf("确认新密码:");cin>>(s2+1);
//          printf("%d\n",strcmp(s1+1,s2+1));
            for(cnt=0;(strcmp(s1+1,s2+1)) && cnt<3;cnt++){printf("密码不一致!请重试:");cin>>(s2+1);}
            if(strcmp(s1+1,s2+1) && cnt>=3){puts("错误次数过多,自动退出。");continue;}
            for(int i=0;i<3;i++) password[i]=EncodePassword(s1,prime[i]);
            puts("重置成功!");
            ofstream fout("password.txt");
            fout<<password[0]<<' '<<password[1]<<' '<<password[2]<<endl;
            fout.close();
        }
    }
    return 0;
}
posted @   Chen_Jinhui  阅读(203)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)

一言

你将不再是道具,而是成为人如其名的人。
——紫罗兰的永恒花园
点击右上角即可分享
微信分享提示