【项目】关于 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 值均为 。 -
支持通过
Contest
命令从同文件夹下的Contest.txt
中导入比赛排名表,同时可选择 Unrated 或 Rated,读取到新用户时可自动注册。 -
支持通过
Standing
命令在控制台输出排名(仅有 Rank,Name,Rating)。
Bug
-
最多仅支持注册
名用户,一个用户名可以被多次注册。 -
所有修改仅在程序中有效,无法保存。
-
当比赛次数过多时整体 Rating 会下降并逐渐变为负 Rating。
-
控制台排名可读性较差。
v(1.1) 更新于 2023/1/29(由于历史久远且存在 Bug 未留存该版本代码和程序)
Update
- 更新 Rating 的计算 Bug。设置“奖励分”。
主要是因为比赛次数超过,所以每场比赛的 Rating 的附加值为 次后就会下降 。
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
- 删除后的用户可再次注册,但是信息会被全部删除。
- 人数支持
。
v(1.4) (开发中)
Update
- 可以保存比赛历史记录及排名。
- 在用户的个人信息下显示他参加过的比赛的信息。(类似于 CodeForces)
结论
该程序还未经过大量测试,未知正确性和性能。欢迎各位体验。
有意的大佬可以私信我获取源码,但是版权仍然归属于 cjh20090318(写
参考文献
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)