uoj-contest-lib.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <?php
  2. define("CONTEST_NOT_STARTED", 0);
  3. define("CONTEST_IN_PROGRESS", 1);
  4. define("CONTEST_PENDING_FINAL_TEST", 2);
  5. define("CONTEST_TESTING", 10);
  6. define("CONTEST_FINISHED", 20);
  7. function calcRating($standings, $K = 400) {
  8. $DELTA = 500;
  9. $n = count($standings);
  10. $rating = array();
  11. for ($i = 0; $i < $n; ++$i) {
  12. $rating[$i] = $standings[$i][2][1];
  13. }
  14. $rank = array();
  15. $foot = array();
  16. for ($i = 0; $i < $n; ) {
  17. $j = $i;
  18. while ($j + 1 < $n && $standings[$j + 1][3] == $standings[$j][3]) {
  19. ++$j;
  20. }
  21. $our_rk = 0.5 * (($i + 1) + ($j + 1));
  22. while ($i <= $j) {
  23. $rank[$i] = $our_rk;
  24. $foot[$i] = $n - $rank[$i];
  25. $i++;
  26. }
  27. }
  28. $weight = array();
  29. for ($i = 0; $i < $n; ++$i) {
  30. $weight[$i] = pow(7, $rating[$i] / $DELTA);
  31. }
  32. $exp = array_fill(0, $n, 0);
  33. for ($i = 0; $i < $n; ++$i) {
  34. for ($j = 0; $j < $n; ++$j) {
  35. if ($j != $i) {
  36. $exp[$i] += $weight[$i] / ($weight[$i] + $weight[$j]);
  37. }
  38. }
  39. }
  40. $new_rating = array();
  41. for ($i = 0; $i < $n; $i++) {
  42. $new_rating[$i] = $rating[$i];
  43. $new_rating[$i] += ceil($K * ($foot[$i] - $exp[$i]) / ($n - 1));
  44. }
  45. for ($i = $n - 1; $i >= 0; $i--) {
  46. if ($i + 1 < $n && $standings[$i][3] != $standings[$i + 1][3]) {
  47. break;
  48. }
  49. if ($new_rating[$i] > $rating[$i]) {
  50. $new_rating[$i] = $rating[$i];
  51. }
  52. }
  53. for ($i = 0; $i < $n; $i++) {
  54. if ($new_rating[$i] < 0) {
  55. $new_rating[$i] = 0;
  56. }
  57. }
  58. return $new_rating;
  59. }
  60. function calcRatingSelfTest() {
  61. $tests = [
  62. [[1500, 1], [1500, 1]],
  63. [[1500, 1], [1600, 1]],
  64. [[1500, 1], [1600, 2], [1600, 2]],
  65. [[1500, 1], [200, 2], [100, 2]],
  66. [[1500, 1], [100, 2], [200, 2]],
  67. [[1500, 1], [100, 2], [200, 3]],
  68. [[1500, 1], [200, 2], [100, 3]],
  69. [[1500, 1], [3000, 2], [1500, 3]],
  70. [[1500, 1], [3000, 2], [1500, 3], [1500, 3]],
  71. [[1500, 1], [1500, 2], [1500, 3], [3000, 4]],
  72. [[1500, 1], [1500, 2], [10, 3], [1, 4]]
  73. ];
  74. foreach ($tests as $test_num => $test) {
  75. print "test #{$test_num}\n";
  76. $standings = array();
  77. $n = count($test);
  78. for ($i = 0; $i < $n; $i++) {
  79. $standings[] = [0, 0, [(string)$i, $test[$i][0]], $test[$i][1]];
  80. }
  81. $new_rating = calcRating($standings);
  82. for ($i = 0; $i < $n; $i++) {
  83. printf("%3d: %4d -> %4d delta: %+4d\n", $test[$i][1], $test[$i][0], $new_rating[$i], $new_rating[$i] - $test[$i][0]);
  84. }
  85. print "\n";
  86. }
  87. }
  88. function genMoreContestInfo(&$contest) {
  89. $contest['start_time_str'] = $contest['start_time'];
  90. $contest['start_time'] = new DateTime($contest['start_time']);
  91. $contest['end_time'] = clone $contest['start_time'];
  92. $contest['end_time']->add(new DateInterval("PT${contest['last_min']}M"));
  93. if ($contest['status'] == 'unfinished') {
  94. if (UOJTime::$time_now < $contest['start_time']) {
  95. $contest['cur_progress'] = CONTEST_NOT_STARTED;
  96. } elseif (UOJTime::$time_now < $contest['end_time']) {
  97. $contest['cur_progress'] = CONTEST_IN_PROGRESS;
  98. } else {
  99. $contest['cur_progress'] = CONTEST_PENDING_FINAL_TEST;
  100. }
  101. } elseif ($contest['status'] == 'testing') {
  102. $contest['cur_progress'] = CONTEST_TESTING;
  103. } elseif ($contest['status'] == 'finished') {
  104. $contest['cur_progress'] = CONTEST_FINISHED;
  105. }
  106. $contest['extra_config'] = json_decode($contest['extra_config'], true);
  107. if (!isset($contest['extra_config']['standings_version'])) {
  108. $contest['extra_config']['standings_version'] = 2;
  109. }
  110. }
  111. function updateContestPlayerNum($contest) {
  112. DB::update("update contests set player_num = (select count(*) from contests_registrants where contest_id = {$contest['id']}) where id = {$contest['id']}");
  113. }
  114. // problems: pos => id
  115. // data : id, submit_time, submitter, problem_pos, score
  116. // people : username, user_rating
  117. function queryContestData($contest, $config = array()) {
  118. mergeConfig($config, [
  119. 'pre_final' => false
  120. ]);
  121. $problems = [];
  122. $prob_pos = [];
  123. $n_problems = 0;
  124. $result = DB::query("select problem_id from contests_problems where contest_id = {$contest['id']} order by problem_id");
  125. while ($row = DB::fetch($result, MYSQLI_NUM)) {
  126. $prob_pos[$problems[] = (int)$row[0]] = $n_problems++;
  127. }
  128. $data = [];
  129. if ($config['pre_final']) {
  130. $result = DB::query("select id, submit_time, submitter, problem_id, result from submissions"
  131. ." where contest_id = {$contest['id']} and score is not null order by id");
  132. while ($row = DB::fetch($result, MYSQLI_NUM)) {
  133. $r = json_decode($row[4], true);
  134. if (!isset($r['final_result'])) {
  135. continue;
  136. }
  137. $row[0] = (int)$row[0];
  138. $row[3] = $prob_pos[$row[3]];
  139. $row[4] = (int)($r['final_result']['score']);
  140. $data[] = $row;
  141. }
  142. } else {
  143. if ($contest['cur_progress'] < CONTEST_FINISHED) {
  144. $result = DB::query("select id, submit_time, submitter, problem_id, score from submissions"
  145. ." where contest_id = {$contest['id']} and score is not null order by id");
  146. } else {
  147. $result = DB::query("select submission_id, date_add('{$contest['start_time_str']}', interval penalty second),"
  148. ." submitter, problem_id, score from contests_submissions where contest_id = {$contest['id']}");
  149. }
  150. while ($row = DB::fetch($result, MYSQLI_NUM)) {
  151. $row[0] = (int)$row[0];
  152. $row[3] = $prob_pos[$row[3]];
  153. $row[4] = (int)$row[4];
  154. $data[] = $row;
  155. }
  156. }
  157. $people = [];
  158. $result = DB::query("select username, user_rating from contests_registrants where contest_id = {$contest['id']} and has_participated = 1");
  159. while ($row = DB::fetch($result, MYSQLI_NUM)) {
  160. $row[1] = (int)$row[1];
  161. $people[] = $row;
  162. }
  163. return ['problems' => $problems, 'data' => $data, 'people' => $people];
  164. }
  165. function calcStandings($contest, $contest_data, &$score, &$standings, $update_contests_submissions = false) {
  166. // score: username, problem_pos => score, penalty, id
  167. $score = array();
  168. $n_people = count($contest_data['people']);
  169. $n_problems = count($contest_data['problems']);
  170. foreach ($contest_data['people'] as $person) {
  171. $score[$person[0]] = array();
  172. }
  173. foreach ($contest_data['data'] as $submission) {
  174. $penalty = (new DateTime($submission[1]))->getTimestamp() - $contest['start_time']->getTimestamp();
  175. if ($contest['extra_config']['standings_version'] >= 2) {
  176. if ($submission[4] == 0) {
  177. $penalty = 0;
  178. }
  179. }
  180. $score[$submission[2]][$submission[3]] = array($submission[4], $penalty, $submission[0]);
  181. }
  182. // standings: rank => score, penalty, [username, user_rating], virtual_rank
  183. $standings = array();
  184. foreach ($contest_data['people'] as $person) {
  185. $cur = array(0, 0, $person);
  186. for ($i = 0; $i < $n_problems; $i++) {
  187. if (isset($score[$person[0]][$i])) {
  188. $cur_row = $score[$person[0]][$i];
  189. $cur[0] += $cur_row[0];
  190. $cur[1] += $cur_row[1];
  191. if ($update_contests_submissions) {
  192. DB::insert("insert into contests_submissions (contest_id, submitter, problem_id, submission_id, score, penalty) values ({$contest['id']}, '{$person[0]}', {$contest_data['problems'][$i]}, {$cur_row[2]}, {$cur_row[0]}, {$cur_row[1]})");
  193. }
  194. }
  195. }
  196. $standings[] = $cur;
  197. }
  198. usort($standings, function($lhs, $rhs) {
  199. if ($lhs[0] != $rhs[0]) {
  200. return $rhs[0] - $lhs[0];
  201. } elseif ($lhs[1] != $rhs[1]) {
  202. return $lhs[1] - $rhs[1];
  203. } else {
  204. return strcmp($lhs[2][0], $rhs[2][0]);
  205. }
  206. });
  207. $is_same_rank = function($lhs, $rhs) {
  208. return $lhs[0] == $rhs[0] && $lhs[1] == $rhs[1];
  209. };
  210. for ($i = 0; $i < $n_people; $i++) {
  211. if ($i == 0 || !$is_same_rank($standings[$i - 1], $standings[$i])) {
  212. $standings[$i][] = $i + 1;
  213. } else {
  214. $standings[$i][] = $standings[$i - 1][3];
  215. }
  216. }
  217. }