@@ -254,7 +254,8 @@ public function calculateScoreRow(
254
254
Contest $ contest ,
255
255
Team $ team ,
256
256
Problem $ problem ,
257
- bool $ updateRankCache = true
257
+ bool $ updateRankCache = true ,
258
+ bool $ updatePotentialFirstToSolves = true
258
259
): void {
259
260
$ this ->logger ->debug (
260
261
"ScoreboardService::calculateScoreRow '%d' '%d' '%d' " ,
@@ -417,6 +418,7 @@ public function calculateScoreRow(
417
418
// See if this submission was the first to solve this problem.
418
419
// Only relevant if it was correct in the first place.
419
420
$ firstToSolve = false ;
421
+ $ potentialFirstToSolve = false ;
420
422
if ($ correctJury ) {
421
423
$ params = [
422
424
'cid ' => $ contest ->getCid (),
@@ -437,32 +439,49 @@ public function calculateScoreRow(
437
439
// - or already judged to be correct (if it is judged but not correct,
438
440
// it is not a first to solve)
439
441
// - or the submission is still queued for judgement (judgehost is NULL).
440
- $ verificationRequiredExtra = $ verificationRequired ? 'OR j.verified = 0 ' : '' ;
442
+ // If there are no valid correct submissions submitted earlier but there
443
+ // are submissions awaiting judgement, we are potentially the first to solve.
444
+ // We need to keep track of this since we later need to set the actual first to solve.
445
+ $ verificationRequiredExtra = ($ verificationRequired && !$ useExternalJudgements ) ? 'OR j.verified = 0 ' : '' ;
441
446
if ($ useExternalJudgements ) {
442
- $ firstToSolve = 0 == $ this -> em -> getConnection ()-> fetchOne ( '
447
+ $ baseQuery = '
443
448
SELECT count(*) FROM submission s
444
449
LEFT JOIN external_judgement ej USING (submitid)
445
450
LEFT JOIN external_judgement ej2 ON ej2.submitid = s.submitid AND ej2.starttime > ej.starttime
446
451
LEFT JOIN team t USING(teamid)
447
452
LEFT JOIN team_category tc USING (categoryid)
448
453
WHERE s.valid = 1 AND
449
- (ej.result IS NULL OR ej.result = :correctResult ' .
450
- $ verificationRequiredExtra .') AND
451
454
s.cid = :cid AND s.probid = :probid AND
452
455
tc.sortorder = :teamSortOrder AND
453
- round(s.submittime,4) < :submitTime ' , $ params );
456
+ round(s.submittime,4) < :submitTime ' ;
457
+ $ judgingTable = 'ej ' ;
454
458
} else {
455
- $ firstToSolve = 0 == $ this -> em -> getConnection ()-> fetchOne ( '
459
+ $ baseQuery = '
456
460
SELECT count(*) FROM submission s
457
461
LEFT JOIN judging j ON (s.submitid=j.submitid AND j.valid=1)
458
462
LEFT JOIN team t USING (teamid)
459
463
LEFT JOIN team_category tc USING (categoryid)
460
464
WHERE s.valid = 1 AND
461
- (j.judgingid IS NULL OR j.result IS NULL OR j.result = :correctResult ' .
462
- $ verificationRequiredExtra .') AND
463
465
s.cid = :cid AND s.probid = :probid AND
464
466
tc.sortorder = :teamSortOrder AND
465
- round(s.submittime,4) < :submitTime ' , $ params );
467
+ round(s.submittime,4) < :submitTime ' ;
468
+ $ judgingTable = 'j ' ;
469
+ }
470
+
471
+ $ numEarlierCorrect = $ this ->em ->getConnection ()->fetchOne (
472
+ "$ baseQuery AND $ judgingTable.result = :correctResult " ,
473
+ $ params );
474
+ $ numEarlierPending = $ this ->em ->getConnection ()->fetchOne (
475
+ "$ baseQuery AND ( $ judgingTable.result IS NULL $ verificationRequiredExtra) " ,
476
+ $ params );
477
+
478
+ if ($ numEarlierCorrect == 0 ) {
479
+ // This is either a first to solve or potential first to solve
480
+ if ($ numEarlierPending == 0 ) {
481
+ $ firstToSolve = true ;
482
+ } else {
483
+ $ potentialFirstToSolve = true ;
484
+ }
466
485
}
467
486
}
468
487
@@ -482,13 +501,14 @@ public function calculateScoreRow(
482
501
'runtimePublic ' => $ runtimePubl === PHP_INT_MAX ? 0 : $ runtimePubl ,
483
502
'isCorrectPublic ' => (int )$ correctPubl ,
484
503
'isFirstToSolve ' => (int )$ firstToSolve ,
504
+ 'isPotentialFirstToSolve ' => (int )$ potentialFirstToSolve ,
485
505
];
486
506
$ this ->em ->getConnection ()->executeQuery ('REPLACE INTO scorecache
487
507
(cid, teamid, probid,
488
508
submissions_restricted, pending_restricted, solvetime_restricted, runtime_restricted, is_correct_restricted,
489
- submissions_public, pending_public, solvetime_public, runtime_public, is_correct_public, is_first_to_solve)
509
+ submissions_public, pending_public, solvetime_public, runtime_public, is_correct_public, is_first_to_solve, is_potential_first_to_solve )
490
510
VALUES (:cid, :teamid, :probid, :submissionsRestricted, :pendingRestricted, :solvetimeRestricted, :runtimeRestricted, :isCorrectRestricted,
491
- :submissionsPublic, :pendingPublic, :solvetimePublic, :runtimePublic, :isCorrectPublic, :isFirstToSolve) ' , $ params );
511
+ :submissionsPublic, :pendingPublic, :solvetimePublic, :runtimePublic, :isCorrectPublic, :isFirstToSolve, :isPotentialFirstToSolve ) ' , $ params );
492
512
493
513
if ($ this ->em ->getConnection ()->fetchOne ('SELECT RELEASE_LOCK(:lock) ' ,
494
514
['lock ' => $ lockString ]) != 1 ) {
@@ -499,6 +519,31 @@ public function calculateScoreRow(
499
519
if ($ updateRankCache && ($ correctJury || $ correctPubl )) {
500
520
$ this ->updateRankCache ($ contest , $ team );
501
521
}
522
+
523
+ // If we did not have a first to solve, we need to check if we have any
524
+ // potential first to solve that are now the first to solve.
525
+ // We only do this if there are no pending submissions for this problem
526
+ // for this team and if this submission is not a potential first to solve itself.
527
+ // We also do this only once, to not have infinite loops if we have multiple
528
+ // potential first to solves.
529
+ if ($ updatePotentialFirstToSolves && $ pendingJury === 0 && !$ potentialFirstToSolve ) {
530
+ /** @var ScoreCache[] $potentialFirstToSolves */
531
+ $ potentialFirstToSolves = $ this ->em ->createQueryBuilder ()
532
+ ->from (ScoreCache::class, 's ' )
533
+ ->join ('s.team ' , 't ' )
534
+ ->select ('s ' , 't ' )
535
+ ->andWhere ('s.contest = :contest ' )
536
+ ->andWhere ('s.problem = :problem ' )
537
+ ->andWhere ('s.is_potential_first_to_solve = 1 ' )
538
+ ->setParameter ('contest ' , $ contest )
539
+ ->setParameter ('problem ' , $ problem )
540
+ ->getQuery ()
541
+ ->getResult ();
542
+
543
+ foreach ($ potentialFirstToSolves as $ row ) {
544
+ $ this ->calculateScoreRow ($ contest , $ row ->getTeam (), $ problem , $ updateRankCache , false );
545
+ }
546
+ }
502
547
}
503
548
504
549
/**
0 commit comments