From 0ad1e66e106e6e70b4b93a844fec1b8980024b84 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sat, 24 Oct 2026 23:48:31 +0000 Subject: [PATCH 1/8] docs: record the PR1.5 merge and the staging import result in the task title index notes Co-Authored-By: Claude Sonnet 5.5 --- .../task-title-index-change-impact/done.md | 6 +- .../investigation.md | 24 +-- .../plan-pr2-5.md | 2 +- .../plan-pr2.md | 168 +++++++++--------- .../task-title-index-change-impact/plan.md | 30 ++-- 5 files changed, 111 insertions(+), 119 deletions(-) diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/done.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/done.md index 389d094a3..a439422e2 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/done.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/done.md @@ -1,6 +1,6 @@ # 完了分(PR0 / PR1 / PR1.5)— 意思決定の要約 -**状態**: PR0 完了(#4212)・PR1 完了(#4217)・PR1.5 実装済み(ブランチ `#4028`、`471ad20c`)。全体像は [plan.md](./plan.md)、根拠データは [investigation.md](./investigation.md)。 +**状態**: PR0 完了(#4212)・PR1 完了(#4217)・PR1.5 完了(#4219)。全体像は [plan.md](./plan.md)、根拠データは [investigation.md](./investigation.md)。 コードと git の履歴から読み取れない判断と事実だけを残す。変更したファイルや関数は #4212 / #4217 と `471ad20c` の差分を見る。PR1.5 は [末尾](#pr15--aoj-api-の移転に追従する4028)。 @@ -41,7 +41,7 @@ PR2 以降の計画には含めていない。 - 2016 年の JAG 模擬国内を API から取り込むと、記号が seed と食い違う(Codex / CodeRabbit)。直すなら、取り込みで 2 回開催を `A` / `B` の `contest_id` に分け、それぞれ A から振る。`getContests` 側もそろえる - seed 整合テストの期待値を `assignProblemLetters` で作っている(Codex)。testing.md の「期待値は literal」に合わない。PR2 では補正したコンテストだけを literal にする(M18)。全件を literal にするかは未判断 - `src/lib/services/tasks.ts` の `title.replace(task_table_index, …)` は、AOJ の問題に ContestTaskPair を作るとタイトル本文を置換する(Codex)。AOJ の pair は現在 0 件。直すときは先頭の `${index}. ` だけを置換し、`Amidakuji` のような本文と `A. Title` の両方を回帰テストに含める -- コンテスト表のスクリーンショットでの目視確認 +- コンテストテーブルのスクリーンショットでの目視確認 --- @@ -76,4 +76,4 @@ PR2 以降の計画には含めていない。 ### 残っている確認 - ~~コンテナを再ビルドした後、ローカルの管理画面で AOJ から取得できること~~ — 2026-10-24 にメンテナが確認した -- staging に入った後の JAG REGIONAL の取り込み(PR2 の前提)が、Vercel 上での実地の確認を兼ねる([plan-pr2.md](./plan-pr2.md#着手前にすること))。念のため、こちらからの取得ツールは新しいホストへの接続を拒否され(`ECONNREFUSED`)、旧ホストには届いて 410 を受けた。新しいホストが一部の送信元を拒んでいる可能性があるので、staging で失敗したら接続元の制限を疑う +- ~~staging(Vercel)での取り込み~~ — 2026-10-24 にメンテナが JAG REGIONAL を取り込み、16 / 158 になった([plan-pr2.md](./plan-pr2.md#範囲))。こちらの取得ツールが新しいホストに `ECONNREFUSED` で拒まれたことから送信元の制限を心配していたが、Vercel からは問題なく取得できた diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/investigation.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/investigation.md index 9bd6b4871..6ba7aa8b6 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/investigation.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/investigation.md @@ -17,7 +17,7 @@ PR0 / PR1 だけに関わる調査(出典欄の呼び出し元、テストの # 1. PR2 の照合(2026-10-24) -メンテナのスプレッドシート(スクリーンショット 16 枚、行 2〜1025。上が新しい)と、本番の問題一覧の JAG 模擬地区(スクリーンショット 10 枚、158 問)を目視で突き合わせた。**目視による暫定の結果**であり、確定は [plan-pr2.md](./plan-pr2.md#2-0-照合) のスクリプトで行う。 +メンテナのスプレッドシート(スクリーンショット 16 枚、行 2〜1025。上が新しい)と、本番の問題一覧の JAG 模擬地区(スクリーンショット 10 枚、158 問)を目視で突き合わせた。**目視による暫定の結果**であり、確定は [plan-pr2.md](./plan-pr2.md#2-2-照合) のスクリプトで行う。 スプレッドシートで折りたたまれた行(40–50 ほか 19 か所)は JAG 模擬国内だけで、合宿系・模擬地区は含まない(メンテナに確認済み)。 @@ -228,17 +228,17 @@ AtCoder 側(`AOJ_*` 以外)で title が `${task_table_index}. ` で始ま ## PR2 の範囲の本番と staging(2026-10-24) -SQL A([plan-pr2.md](./plan-pr2.md#着手前の確認2026-10-24-に回答済み))の結果。 +本番と staging で範囲の状態を集計した結果(SQL は [plan-pr2.md](./plan-pr2.md#確かめ方))。 -| 範囲 | 本番 | staging | index が問題 ID のまま | title が問題 ID 付き | -| -------- | -------- | ---------- | ---------------------- | -------------------- | -| Regional | 16 / 158 | **2 / 16** | 全行 | 0 行 | -| Spring | 4 / 42 | 4 / 42 | 全行 | 全行 | -| Summer | 36 / 338 | 36 / 338 | 全行 | 全行 | -| Winter | 10 / 87 | 10 / 87 | 全行 | 全行 | +| 範囲 | 本番 | staging | index が問題 ID のまま | title が問題 ID 付き | +| -------- | -------- | ------------------------------- | ---------------------- | -------------------- | +| Regional | 16 / 158 | 16 / 158(取り込み前は 2 / 16) | 全行 | 0 行 | +| Spring | 4 / 42 | 4 / 42 | 全行 | 全行 | +| Summer | 36 / 338 | 36 / 338 | 全行 | 全行 | +| Winter | 10 / 87 | 10 / 87 | 全行 | 全行 | -- staging には模擬地区が 2 コンテストしか無い。PR2 の前に staging で取り込み、本番とそろえる -- 本番の模擬地区 158 問(SQL B)は、AOJ の一覧のスクリーンショットと問題名・問題 ID がすべて一致した。grade が付いているのは 6 問 +- staging には当初、模擬地区が 2 コンテスト / 16 問しか無かった。PR1.5(#4219)の後に staging で取り込み、本番と同じ 16 / 158 にそろえた(2026-10-24) +- 本番の模擬地区 158 問([plan-pr2.md](./plan-pr2.md#2-1-seed-に模擬地区を足す) の SQL で取り出したもの)は、AOJ の一覧のスクリーンショットと問題名・問題 ID がすべて一致した。grade が付いているのは 6 問 - 本番の 3355 の title の末尾にタブがある(`Most distant point from the stations\t`)。前後に空白のある title はほかにもある(下記) ## 前後に空白のある title @@ -285,7 +285,7 @@ AOJ 問題 ID の連番性を調べると、seed の 129 コンテスト中 128 **プレフィックスは合宿系の 467 件すべてで `task_id` と完全に一致していた(不一致 0 件)。** したがって除去も復元も機械的に安全に行える。 -模擬地区は管理画面の取り込みで本番に入っており、取り込みは導入時(2024-10、`7dcc5e4c`)から `problem.name` をそのまま title にしている。SQL A でもプレフィックスは 0 行だった(本番・staging)。 +模擬地区は管理画面の取り込みで本番に入っており、取り込みは導入時(2024-10、`7dcc5e4c`)から `problem.name` をそのまま title にしている。範囲の集計でもプレフィックスは 0 行だった(本番・staging)。 退行の機序(title を直さずに index だけ記号にした場合): @@ -305,7 +305,7 @@ PR2 後: index='A', title='2389. Asynchronous Exceptions' | AOJ ICPC/JAG(更新対象) | 1,099 | **無条件に可能**。`task_table_index == task_id` が例外ゼロで成立(seed、PR1 の前) | | その他(AtCoder 等) | 1,367 | **条件付き**。84.2%(1,151 件)は `task_id` のサフィックスから導出できるが、216 件は例外 | -本番だけにある模擬地区 158 件も、SQL A で全行 `task_table_index == task_id` だった(2026-10-24)。 +本番にある模擬地区 158 件も、範囲の集計で全行 `task_table_index == task_id` だった(2026-10-24)。 導出できない 216 件の内訳: `abc` 41(`abc318_h` に対し index は `Ex`)、`typical` 32、 `APG` 27(`APG4bPython_co` に対し index は `EX8`)、`arc` 27、`tdpc` 20、 `math-and-algorithm` 15、ほか(`task_id` にサフィックスが無い AOJ 大学系 29 件を含む)。 diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2-5.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2-5.md index 0dfbbb58f..e8434600e 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2-5.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2-5.md @@ -1,4 +1,4 @@ -# PR2.5 — 合宿系と JAG 模擬地区をコンテスト表に出す +# PR2.5 — 合宿系と JAG 模擬地区のコンテスト別のテーブルを作る 前提と共通の決定は [plan.md](./plan.md)。並び順の一覧は [investigation.md](./investigation.md#表示順76-コンテスト)。 diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md index de0254ce7..d7324b9a3 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md @@ -1,79 +1,63 @@ # PR2 — JAG 合宿系と JAG 模擬地区を問題記号にする(66 コンテスト / 625 問) -前提・共通の決定・データ移行の手順は [plan.md](./plan.md)。照合結果は [investigation.md](./investigation.md#1-pr2-の照合2026-10-24)。 +AOJ の JAG 合宿系(夏 / 冬 / 春)と JAG 模擬地区の `task_table_index` を、AOJ の問題 ID から問題記号(A, B, C…)に変える。PR1 で ICPC / JAG 模擬国内に行ったことを、残りの JAG に広げる。 -**状態**: 未着手。着手前の確認(Q1 / Q2)は 2026-10-24 に回答済み。[PR1.5](./done.md#pr15--aoj-api-の移転に追従する4028) の後に、[着手前にすること](#着手前にすること)を済ませてから始める。 +全体の目的・共通の決定(M 番号)・データ移行の共通手順は [plan.md](./plan.md)、根拠データは [investigation.md](./investigation.md)。 + +**状態**: 未着手。着手に必要なデータと環境(staging の件数)はそろっている。 ## 範囲 -| 範囲 | コンテスト / 問題 | 所在 | 今の `title` | 取り込みの経路 | -| -------------------- | ----------------- | ----------------------------- | --------------------------- | -------------------------------------------- | -| JAG 夏 / 冬 / 春合宿 | 50 / 467 | seed・staging・本番 | `'2389. …'`(問題 ID 付き) | 無い(`JagRound` は PRELIM / REGIONAL のみ) | -| JAG 模擬地区 | 16 / 158 | 本番のみ(staging は 2 / 16) | 素 | 管理画面(今は問題 ID のまま入る) | +| 範囲 | コンテスト / 問題 | ある場所 | 今の `task_table_index` | 今の `title` | +| -------------------- | ----------------- | ---------------------------------- | ----------------------- | --------------------------------- | +| JAG 夏 / 冬 / 春合宿 | 50 / 467 | seed・staging・本番 | 問題 ID | `'2389. …'`(問題 ID が前に付く) | +| JAG 模擬地区 | 16 / 158 | staging・本番(**seed には無い**) | 問題 ID | 素 | + +PR2 の後は、625 問すべてで `task_table_index` が問題記号になり、合宿系の title は素になる。管理画面の取り込みでも、JAG は全 round で記号を付けるようになる。 -PR2 の時点では、この範囲のコンテスト表はまだ無い(PR2.5 で足す)。表示は問題一覧の出典欄(例: `JAG 夏合宿 2024 Day3 - H(AOJ)`)と title で確かめる。 +範囲外: -関係する決定: M2 / M6 / M8 / M9 / M12 / M13 / M14 / M18([plan.md](./plan.md#決定事項))。 +- コンテスト別のテーブルの作成(PR2.5) +- DB に無いコンテストの追加(PR2.1) +- AOJ のコンテスト一覧に無い問題の補完(PR3) +- 前後に空白のある title(本番 46 件。3355 の末尾のタブを含む。[plan.md](./plan.md#スコープ外)) -## 起きること +## 知っておくこと -| 変更 | 起きること | 対処 | -| ------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------ | -| `task_table_index` の値が変わる | 既存レコードは自動更新されない(seed も取り込みも「既存なら skip」) | 移行 UPDATE | -| 合宿系の title の問題 ID | index だけ記号にすると `removeTaskIndexFromTitle` が空振りし、`'2389. …'` が出典欄やセルにそのまま出る | title も直す(M9) | -| 基本ルールで記号がずれる | 8 コンテストで先頭・途中に欠落がある([照合結果](./investigation.md#記号の補正が要るコンテスト)) | 補正(M13) | +- **既存の行は自動では変わらない**。seed も取り込みも「既存なら skip」なので、移行 SQL で直す(M2) +- **記号は基本ルール(コンテスト内で問題 ID の昇順に A, B, C…)で付く。ただし 8 コンテストは例外**。先頭や途中の問題が AOJ に無く、そのまま振ると記号がずれる。正しい記号はメンテナのスプレッドシートの位置で決める(M13。一覧は [investigation.md](./investigation.md#記号の補正が要るコンテスト)。8 コンテスト / 45 行) +- **合宿系の title は直す**。index だけ記号にすると、表示側の `removeTaskIndexFromTitle`(先頭の `${index}. ` を外す)が空振りし、`'2389. …'` が出典欄やセルに出る。467 件すべてで、前に付いた番号は自分の `task_id` と一致する(M9) +- **一意性は壊れない**。`id` / `task_id` は `task_table_index` を使っておらず、FK もすべて `task_id` を参照する([根拠](./investigation.md#一意性は壊れない)) +- **データの状態**: 本番と staging は同じで(2026-10-24 に集計。SQL は[確かめ方](#確かめ方))、範囲の全行で index が問題 ID のまま。title に問題 ID が付いているのは合宿系だけ -- 一意性は壊れない。`id` / `task_id` は `problem_index` を入力にしておらず、FK もすべて `task_id` を参照する([根拠](./investigation.md#一意性は壊れない)) -- 模擬地区の title にプレフィックスは無い(本番・staging とも 0 行)。本番の 3355 の title の末尾にタブがあるが、本件では直さない(前後に空白のある title 46 件と合わせて残タスク。[plan.md](./plan.md#スコープ外)) +| kind | 本番・staging(コンテスト / 問題) | index が問題 ID のまま | title が問題 ID 付き | +| -------- | ---------------------------------- | ---------------------- | -------------------- | +| Regional | 16 / 158 | 全行 | 0 行 | +| Spring | 4 / 42 | 全行 | 全行 | +| Summer | 36 / 338 | 全行 | 全行 | +| Winter | 10 / 87 | 全行 | 全行 | ## タスク -| # | 内容 | 補足 | -| --- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| 2-0 | 対応表の生成と照合 | [下記](#2-0-照合) | -| 2-1 | seed に模擬地区 158 問を追加する(M12) | SQL B の値(grade も本番のまま。title は取り込みと同じく trim する)。最初は本番と同じ形(index = 問題 ID)で入れ、2-0 の照合の入力にする | -| 2-2 | 取り込みで、JAG は全 round で記号を付ける(M14) | `AojChallengesApiClient` の `usesProblemLetters` を `contestType === 'ICPC' \|\| contestType === 'JAG'` にする | -| 2-3 | `prisma/tasks.ts` の 625 件の `problem_index` と、合宿系 467 件の `title` を書き換える | 2-0 の出力で置き換える | -| 2-4 | 移行 SQL | [下記](#移行-sql) | -| 2-5 | テスト | [下記](#テスト) | -| 2-6 | 手順書 | `add-tasks` の「ICPC / JAG 模擬国内の `problem_index` は問題記号にする」を JAG 全体に広げる | -| 2-7 | 照合スクリプトを削除する | 追加とは別のコミットにし、git の履歴から辿れるようにする | +| # | 内容 | 補足 | +| --- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| 2-1 | seed に模擬地区 158 問を追加する(M12) | [下記](#2-1-seed-に模擬地区を足す) | +| 2-2 | 対応表を作り、スプレッドシートと照合する(M6) | [下記](#2-2-照合) | +| 2-3 | 取り込みで、JAG は全 round で記号を付ける(M14) | `AojChallengesApiClient` の `usesProblemLetters` を `contestType === 'ICPC' \|\| contestType === 'JAG'` にする | +| 2-4 | `prisma/tasks.ts` の 625 件の `problem_index` と、合宿系 467 件の `title` を書き換える | 2-2 の出力で置き換える | +| 2-5 | 移行 SQL | [下記](#2-5-移行-sql) | +| 2-6 | テスト | [下記](#2-6-テスト) | +| 2-7 | 手順書 | `add-tasks` の「ICPC / JAG 模擬国内の `problem_index` は問題記号にする」を JAG 全体に広げる | +| 2-8 | 照合スクリプトを削除する | 追加とは別のコミットにし、git の履歴から辿れるようにする | **クロスレビューが必須**(データ移行)。 -## 着手前の確認(2026-10-24 に回答済み) - -Q1: SQL A を本番と staging で流した結果。 - -| kind | 本番(コンテスト / 問題) | staging(コンテスト / 問題) | index が問題 ID のまま | title が問題 ID 付き | -| -------- | ------------------------- | ---------------------------- | ---------------------- | -------------------- | -| Regional | 16 / 158 | **2 / 16** | 全行 | 0 行 | -| Spring | 4 / 42 | 4 / 42 | 全行 | 全行 | -| Summer | 36 / 338 | 36 / 338 | 全行 | 全行 | -| Winter | 10 / 87 | 10 / 87 | 全行 | 全行 | +### 2-1 seed に模擬地区を足す -- 本番は想定どおり。移行 SQL の想定値は (1) 625、(2) 45、(3) 467 で確定した -- **staging の模擬地区は 2 / 16 しか無い**。このままでは (1) が 483 になり RAISE する。[取り込みでそろえる](#着手前にすること) - -Q2: SQL B の結果(本番の模擬地区 158 問)を受け取った。2-1 の seed の元にする。 - -- grade が PENDING でないものが 6 問(2251 D3、2594 D3、2736 D5、2858 D1、2863 D2、2865 D4) -- 3355 の title の末尾にタブがある(`Most distant point from the stations\t`)。AOJ の API がそのまま返す値で、取り込みが `trim()` するようになる前(2026-05-18 より前)に入った行。本番で調べると、前後に空白のある title は 46 件あった([詳細](./investigation.md#前後に空白のある-title))。PR2 では直さない([plan.md](./plan.md#スコープ外)) +本番から次の SQL で取り出した値を、まず本番と同じ形(index = 問題 ID)で `prisma/tasks.ts` に足す。2-2 の照合はこれを読む。記号への書き換えは 2-4 で合宿系と一緒に行う。 ```sql --- SQL A: state of the PR2 scope (run on production and staging). -SELECT substring(contest_id FROM '^JAG(Summer|Winter|Spring|Regional)') AS kind, - count(DISTINCT contest_id) AS contests, - count(*) AS tasks, - count(*) FILTER (WHERE task_table_index = task_id) AS index_is_problem_id, - count(*) FILTER (WHERE starts_with(title, task_id || '. ')) AS title_has_id_prefix -FROM task -WHERE contest_type = 'AOJ_JAG' - AND contest_id ~ '^JAG(Summer|Winter|Spring|Regional)' -GROUP BY kind -ORDER BY kind; - --- SQL B: JAG Regional tasks for the seed (production). +-- JAG Regional tasks for the seed (run on production). SELECT task_id, contest_id, task_table_index, title, grade FROM task WHERE contest_type = 'AOJ_JAG' @@ -81,27 +65,22 @@ WHERE contest_type = 'AOJ_JAG' ORDER BY contest_id, task_id::int; ``` -## 着手前にすること - -| # | 誰が | 内容 | 理由 | -| --- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | メンテナ | PR1.5 が staging に入った後、staging の管理画面から JAG REGIONAL を取り込み、SQL A で Regional が 16 / 158 になったことを確かめる | 件数チェックを staging で通すため。既存の 16 問は skip され、残りの 142 問が問題 ID のまま入る。**PR2 の PR を開く前**に行う。PR2 の CI は staging へのマージ前に staging の DB へマイグレーションを流す([CI での適用](./plan.md#ci-での適用))。PR2 のコードで取り込むと基本ルールの記号が入り、2005 / 2013 がずれる | +- grade は本番の値のまま入れる(PENDING でないのは 6 問: 2251 D3、2594 D3、2736 D5、2858 D1、2863 D2、2865 D4) +- title は取り込みと同じく trim する(3355 の末尾のタブを落とす) -取り込みは PR1.5 の実地の確認も兼ねる。旧ホストが 410 を返すので、PR1.5 の前は取り込めない。 +### 2-2 照合 -## 2-0 照合 +PR1 のスクリプト(`git show 171bf3c9:docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_problem_letters.ts`。仕組みは [done.md](./done.md#検証で分かったこと))を次のように直して使う。 -PR1 のスクリプト(`git show 171bf3c9:docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_problem_letters.ts`)を次のように直して使う。 +- 対象は `^JAG(Summer|Winter|Spring|Regional)` の 66 コンテスト / 625 問。件数が合わなければ止める +- スプレッドシートは 2026-10-24 のスクリーンショット(16 枚、行 2〜1025)から書き写す。PR2.1 の 10 コンテストは含めない +- 記号はスプレッドシートの位置(欠けている問題を飛ばした位置)から決める。PR1 は基本ルールで記号を付けてから照合したが、PR2 には先頭・途中の欠落があるのでそうはいかない +- 基本ルールと違うコンテストを別に出力し、2-5 の補正の `VALUES` に使う。期待する結果は 8 コンテスト / 45 行(目視の暫定値。合わなければ、どちらが正しいかを個別に確かめる) +- 夏合宿 2010 Day2 の J(`Person responsible for problem description don't w`)は、AOJ の title 自体も同じ所で切れている。前方一致の扱いは要らない -- 対象は `^JAG(Summer|Winter|Spring|Regional)` の 66 コンテスト / 625 問。件数が合わなければ止める。模擬地区は 2-1 で seed に足してから読む。PR2.1 の 10 コンテストは含めない -- 書き写しの元は 2026-10-24 のスクリーンショット(スプレッドシート 16 枚、行 2〜1025) -- PR1 では記号を基本ルールで付け、位置ごとに照合した。PR2 では先頭・途中の欠落(`absentLetters`)があるので、記号はスプレッドシートの位置(欠落を飛ばした位置)から決める。基本ルールと違うコンテストは別に出力し、移行 SQL の補正(`VALUES`)に使う -- 期待する出力: 補正が要るのは 8 コンテスト / 45 行(目視の暫定値)。合わなければ、どちらが正しいかを個別に確かめる -- 夏合宿 2010 Day2 の J(`Person responsible for problem description don't w`)は、スプレッドシートのセルだけでなく AOJ の title 自体も同じ所で切れている。前方一致の扱いは要らない +### 2-5 移行 SQL -## 移行 SQL - -骨組みのみ。補正の `VALUES`(45 行)は 2-0 の出力で埋める。3 つの UPDATE を 1 つの DO ブロックに入れ、各件数を検査する(M13)。 +骨組みのみ。補正の `VALUES`(45 行)は 2-2 の出力で埋める。3 つの UPDATE を 1 つの DO ブロックに入れ、それぞれの更新件数を検査する。想定外の件数なら RAISE して、マイグレーション全体を戻す(M13。UPDATE は対象が 0 件でもエラーにならないため)。 ```sql DO $$ @@ -163,24 +142,37 @@ BEGIN END $$; ``` -- **範囲は `contest_id` の接頭辞で絞る**。`AOJ_JAG` には PR1 で済んだ模擬国内も含まれる -- **件数の想定値に 0 を含める理由**: `prisma migrate reset`(ローカル)は空の DB にマイグレーションを流してから seed を入れる。開発者の既存 DB は合宿系だけを持ち、模擬地区は無い(467 / 32) -- **想定値**: 本番の SQL A で確定した(模擬地区の title にプレフィックスは無いので、(3) は 467 のまま)。staging は取り込みでそろえてから PR を開く -- **title の条件**: 「自分の `task_id` + `. `」で始まるものだけを対象にする。`~ '^[0-9]+\. '` だと他の数字で始まる title も消しうる。467 件すべてでプレフィックスが `task_id` と一致することは確認済み([実測](./investigation.md#title-の持ち方が-aoj-内で割れている)) -- **`task_table_index = task_id` の条件**: PR2.1 の CSV(記号付き・素の title)が先に入っていても (1)(3) の対象にならない。PR2.1 との順序を問わないための保険 -- `task_id::int` は安全(模擬地区の問題 ID も 2024〜3357 で数値)。1 コンテスト最大 14 問なので `chr(64 + n)` は A〜Z に収まる。SQL の読み方は [investigation.md](./investigation.md#sql-の読み方) +- **範囲は `contest_id` の接頭辞で絞る**。`AOJ_JAG` には、PR1 で記号にした模擬国内も含まれる +- **件数の想定値**: staging / 本番は 625 / 45 / 467(上の表から)。0 を含めるのは、`prisma migrate reset`(ローカル)が空の DB にマイグレーションを流してから seed を入れるため。開発者の既存の DB は合宿系だけを持つので 467 / 32 / 467 になる +- **title の条件**: 「自分の `task_id` + `. `」で始まるものだけを対象にする。`~ '^[0-9]+\. '` だと、番号で始まる本来の title まで削りうる +- **`task_table_index = task_id` の条件**: PR2.1 の行(記号付き・素の title で入る)が先にあっても、(1)(3) の対象にならない。PR2.1 と順序を問わないための保険 +- `task_id::int` は安全(範囲の問題 ID はすべて数値)。1 コンテスト最大 14 問なので、`chr(64 + n)` は A〜Z に収まる。SQL の読み方は [investigation.md](./investigation.md#sql-の読み方) + +#### 確かめ方 -### ローカルと staging での確認 +[共通の手順](./plan.md#手順)(ローカルで `UPDATE` を `SELECT` に置き換えて「現在値 → 変更後」を見る、など)に加えて: -[共通の手順](./plan.md#手順)に加えて: +- PR2 の seed の模擬地区は記号で入っている(2-1 で足し、2-4 で記号にする)ので、seed から作った DB では移行 SQL の模擬地区の分を確かめられない。PR2 のコードに切り替える前の DB に、管理画面から JAG REGIONAL を取り込んでおく(問題 ID のまま入る)。そうすればローカルでも 625 / 45 / 467 になる +- 補正する 8 コンテストは、「現在値 → 変更後」を全行見る +- 問題一覧の出典欄で、合宿系・模擬地区が `JAG 夏合宿 2024 Day3 - H(AOJ)` のように記号になり、title に問題 ID が出ていないこと(この範囲のコンテストテーブルは PR2.5 まで無い) +- **PR を開く前に**、staging で次の SQL を流し、上の表と同じ件数であることを確かめる。PR の CI は、staging へのマージ前に staging の DB へマイグレーションを流す([CI での適用](./plan.md#ci-での適用))。件数が違うと RAISE でデプロイが止まる -- ローカルの seed には模擬地区が無い(2-1 で足すのは記号付きの値)。PR2 のコードに切り替える前に、管理画面から JAG REGIONAL を取り込んでおく(index が問題 ID のまま、本番と同じ形で入る)。そうすればローカルでも 625 / 45 / 467 になる -- staging は、[着手前にすること](#着手前にすること)の 1 で 16 / 158 になったことを確かめてから PR を開く。件数が想定と一致しないと RAISE でデプロイが止まる -- 補正した 8 コンテストは「現在値 → 変更後」を全行見る -- 出典欄(問題一覧)で、合宿系・模擬地区が記号になり、title に問題 ID が出ていないこと +```sql +-- State of the PR2 scope (run on staging before opening the PR). +SELECT substring(contest_id FROM '^JAG(Summer|Winter|Spring|Regional)') AS kind, + count(DISTINCT contest_id) AS contests, + count(*) AS tasks, + count(*) FILTER (WHERE task_table_index = task_id) AS index_is_problem_id, + count(*) FILTER (WHERE starts_with(title, task_id || '. ')) AS title_has_id_prefix +FROM task +WHERE contest_type = 'AOJ_JAG' + AND contest_id ~ '^JAG(Summer|Winter|Spring|Regional)' +GROUP BY kind +ORDER BY kind; +``` -## テスト +### 2-6 テスト -- `src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts` の範囲を 145 コンテスト / 1,257 問に広げる。補正していないコンテストは `assignProblemLetters` と一致し、補正した 8 コンテストは期待値を literal で書く(M18)。合宿系の title が `${problem id}. ` で始まらないことも見る -- `clients.test.ts`: JAG REGIONAL の取り込みで記号が付くこと。`usesProblemLetters` の JAG 条件を PRELIM だけに戻す変異を検出できること -- 書き換えたテストと追加したテストに `verify-test-strength` を当てる。期待値を新形式へ一括置換するとテストが仕様を検証しなくなるので、`testing.md` の Test integrity に従う +- `src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts` の範囲を 145 コンテスト / 1,257 問に広げる。補正していないコンテストは `assignProblemLetters` と一致すること、補正した 8 コンテストは literal の期待値と一致することを見る(M18)。合宿系の title が `${problem id}. ` で始まらないことも見る +- `clients.test.ts`: JAG REGIONAL の取り込みで記号が付くこと。`usesProblemLetters` の JAG の条件を PRELIM だけに戻す変異を検出できること +- 書き換えたテストと足したテストに `verify-test-strength` を当てる。期待値を新しい形へ一括置換すると、テストが仕様を検証しなくなるので、`testing.md` の Test integrity に従う diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md index 5a942ca2e..9697b63fc 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md @@ -1,6 +1,6 @@ # AOJ ICPC / JAG の `problem_index` を A/B/C 形式へ変更する -**日付**: 2026-09-09 作成、2026-10-24 に PR2 以降を再構成/ **状態**: PR0・PR1 完了。PR1.5 実装済み(ブランチ `#4028`、未マージ)。PR2 以降は未着手 +**日付**: 2026-09-09 作成、2026-10-24 に PR2 以降を再構成/ **状態**: PR0・PR1・PR1.5(#4219)完了。PR2 は着手前の準備が済み、未着手 このファイルは全体像と、PR をまたぐ決定・手順だけを書く。各 PR の作業はそれぞれのファイルにある。 @@ -8,7 +8,7 @@ | -------------------------------------- | ---------------------------------------------------- | | [plan-pr2.md](./plan-pr2.md) | PR2: 合宿系と JAG 模擬地区を記号にする(データ移行) | | [plan-pr2-1.md](./plan-pr2-1.md) | PR2.1: DB に無い 10 コンテストを入れる | -| [plan-pr2-5.md](./plan-pr2-5.md) | PR2.5: コンテスト表に出す(Provider) | +| [plan-pr2-5.md](./plan-pr2-5.md) | PR2.5: コンテスト別のテーブルの作成(Provider) | | [plan-pr3.md](./plan-pr3.md) | PR3: 欠落問題の補完 | | [done.md](./done.md) | PR0 / PR1 / PR1.5 の決定の要約と残タスク | | [investigation.md](./investigation.md) | 根拠データ。PR2 の照合結果、表示順、欠落の一覧など | @@ -19,31 +19,31 @@ > **問題記号**: `A`, `B`, `C`… のように、そのコンテスト内で何番目の問題かを表す記号。AtCoder では `abc300_a` の `a` にあたる。AOJ には存在しない(難所 1)。コード上は `letter`。 -| | 変更前 | 目標 | -| ------------------- | ---------------------------------------- | -------------------------------------------- | -| **G1** 出典欄 | `AOJ 3467(ICPC 国内予選 2024)` | `ICPC 国内予選 2024 - F(AOJ)` | -| **G2** コンテスト表 | 問題 ID 3467 → ソート位置から `F` を算出 | `task_table_index` の `F` を**そのまま使う** | +| | 変更前 | 目標 | +| ------------------------- | ---------------------------------------- | -------------------------------------------- | +| **G1** 出典欄 | `AOJ 3467(ICPC 国内予選 2024)` | `ICPC 国内予選 2024 - F(AOJ)` | +| **G2** コンテストテーブル | 問題 ID 3467 → ソート位置から `F` を算出 | `task_table_index` の `F` を**そのまま使う** | ## PR1 完了時点の状態 -| 範囲(コンテスト / 問題) | `task_table_index` | `title` | 管理画面の取り込み | コンテスト表 | -| ----------------------------------------------------- | ------------------ | --------------------------- | ------------------ | ------------ | -| ICPC 国内予選 / アジア地区 / JAG 模擬国内(79 / 632) | 記号 | 素 | 記号を付ける | あり | -| JAG 夏 / 冬 / 春合宿(50 / 467。seed と本番) | 問題 ID | `'2389. …'`(問題 ID 付き) | 経路が無い | 無し | -| JAG 模擬地区(16 / 158。本番のみ。staging は 2 / 16) | 問題 ID | 素 | 問題 ID のまま | 無し | +| 範囲(コンテスト / 問題) | `task_table_index` | `title` | 管理画面の取り込み | コンテストテーブル | +| ------------------------------------------------------ | ------------------ | --------------------------- | ------------------ | ------------------ | +| ICPC 国内予選 / アジア地区 / JAG 模擬国内(79 / 632) | 記号 | 素 | 記号を付ける | あり | +| JAG 夏 / 冬 / 春合宿(50 / 467。seed と本番) | 問題 ID | `'2389. …'`(問題 ID 付き) | 経路が無い | 無し | +| JAG 模擬地区(16 / 158。本番と staging。seed は 0 件) | 問題 ID | 素 | 問題 ID のまま | 無し | -表示側の変換は PR1 で削除済みで、コンテスト表のセルは `task_table_index` から `A. タイトル` を組み立てる。出典欄の書式(G1)は AOJ 全体に入っているので、合宿系・模擬地区の出典欄は今 `JAG 夏合宿 2024 Day3 - 3644(AOJ)` のように問題 ID を出している。 +表示側の変換は PR1 で削除済みで、コンテストテーブルのセルは `task_table_index` から `A. タイトル` を組み立てる。出典欄の書式(G1)は AOJ 全体に入っているので、合宿系・模擬地区の出典欄は今 `JAG 夏合宿 2024 Day3 - 3644(AOJ)` のように問題 ID を出している。 -AOJ の API は 2026-09-07 に別のホストへ移り、旧ホストは 410 を返す。PR1.5 で追従したが、staging / 本番に入るまでは**管理画面から AOJ を取り込めない**。 +AOJ の API は 2026-09-07 に別のホストへ移り、旧ホストは 410 を返す。PR1.5(#4219)で追従し、staging では取り込めることを確かめた。本番は `main` への昇格まで取り込めない。 ## PR の分割と順序 | | 範囲 | コンテスト | 問題数 | 内容 | | ----------------------------------------------------- | ------------------------------------------------------- | ---------- | ------------------------- | --------------------------------------------------------- | -| [PR1.5](./done.md#pr15--aoj-api-の移転に追従する4028) | AOJ の API クライアント | — | — | ベース URL を新しいホストへ変える(#4028)。実装済み | +| [PR1.5](./done.md#pr15--aoj-api-の移転に追従する4028) | AOJ の API クライアント | — | — | ベース URL を新しいホストへ変える(#4028)。完了(#4219) | | [PR2](./plan-pr2.md) | JAG 夏合宿 / 冬合宿 / 春合宿 + JAG 模擬地区 | 66 | 625 | 既存データを記号にする(seed・移行 SQL・title・取り込み) | | [PR2.1](./plan-pr2-1.md) | 2025 / 2026 の夏合宿(AOJ)+ in AtCoder の 4 コンテスト | 10 | 114(スプレッドシート上) | 新しいデータを入れる(`ATCODER_JAG` の新設を含む) | -| [PR2.5](./plan-pr2-5.md) | PR2 / PR2.1 の 76 コンテスト | 76 | — | グループ「JAG 模擬地区」でコンテスト表に出す | +| [PR2.5](./plan-pr2-5.md) | PR2 / PR2.1 の 76 コンテスト | 76 | — | グループ「JAG 模擬地区」に、コンテスト別のテーブルを作る | | [PR3](./plan-pr3.md) | 欠落問題の補完 | — | — | AOJ のコンテスト一覧に無い問題をどう埋めるか | PR1 と PR2 で、記号にするのは 1,257 問 / 145 コンテストになる。PR2 で完結し、PR3 に進まなくても機能として成立する。 From 7805817299f0211ab4a82874f22af0079e757a59 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sat, 24 Oct 2026 23:55:32 +0000 Subject: [PATCH 2/8] docs: update the PR2 plan Co-Authored-By: Claude Sonnet 5.5 --- .../2026-09-09/task-title-index-change-impact/plan-pr2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md index d7324b9a3..3afd5c0e6 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md @@ -155,7 +155,7 @@ END $$; - PR2 の seed の模擬地区は記号で入っている(2-1 で足し、2-4 で記号にする)ので、seed から作った DB では移行 SQL の模擬地区の分を確かめられない。PR2 のコードに切り替える前の DB に、管理画面から JAG REGIONAL を取り込んでおく(問題 ID のまま入る)。そうすればローカルでも 625 / 45 / 467 になる - 補正する 8 コンテストは、「現在値 → 変更後」を全行見る - 問題一覧の出典欄で、合宿系・模擬地区が `JAG 夏合宿 2024 Day3 - H(AOJ)` のように記号になり、title に問題 ID が出ていないこと(この範囲のコンテストテーブルは PR2.5 まで無い) -- **PR を開く前に**、staging で次の SQL を流し、上の表と同じ件数であることを確かめる。PR の CI は、staging へのマージ前に staging の DB へマイグレーションを流す([CI での適用](./plan.md#ci-での適用))。件数が違うと RAISE でデプロイが止まる +- **PR を開く前に**、staging で次の SQL を流し、上の表と同じ件数であることを確かめる。PR の CI は、staging へのマージ前に staging の DB へマイグレーションを流す([CI での適用](./plan.md#ci-での適用))。件数が違うと RAISE でデプロイが止まる。**済(2026-10-24)**: 上の表と同じ件数だった(Regional 16 / 158、Spring 4 / 42、Summer 36 / 338、Winter 10 / 87。index は全行が問題 ID のまま、title の問題 ID は合宿系の全行だけ)。PR を開くまでに staging の AOJ の問題を足したり消したりした場合は、もう一度流す ```sql -- State of the PR2 scope (run on staging before opening the PR). From c605d3bd980a90a4b63a346bcf68a1d06a4fb100 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 00:04:52 +0000 Subject: [PATCH 3/8] docs: add the PR2 problem letter cross-check script for JAG camps and Regional Transcribes the maintainer's spreadsheet for 66 contests and derives each letter from its spreadsheet position, skipping problems absent from AOJ. All 625 titles match; 8 contests / 45 rows differ from the basic rule. Injected errors (absent letter dropped, order swap, wrong title, short absent list, wrong truncated prefix) are all reported. Co-Authored-By: Claude Opus 5.5 --- .../generate_aoj_jag_problem_letters.ts | 977 ++++++++++++++++++ 1 file changed, 977 insertions(+) create mode 100644 docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts b/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts new file mode 100644 index 000000000..b900de2c1 --- /dev/null +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts @@ -0,0 +1,977 @@ +/** + * Throwaway script for PR2 (see plan-pr2.md). Committed once for traceability, then deleted. + * + * Builds the AOJ problem id -> problem letter table for the JAG training camps and JAG Regional + * from prisma/tasks.ts. Letters come from the position in the maintainer's problem list (a + * spreadsheet) transcribed in OFFICIAL_CONTESTS, skipping problems absent from AOJ's contest + * listing. Prints the table as JSON only when every title matches. + * + * Usage: + * pnpm exec tsx docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts + */ +import { tasks } from '../../../../prisma/tasks'; + +const TARGET_CONTEST_ID = /^JAG(Summer|Winter|Spring|Regional)\d{4}(-day\d+[A-Z]?)?$/; +const EXPECTED_CONTEST_COUNT = 66; +const EXPECTED_TASK_COUNT = 625; +const TRUNCATION_MARK = '…'; + +interface OfficialContest { + /** Official titles in letter order. A trailing "…" marks a title cut off in the spreadsheet. */ + titles: string[]; + /** Letters of official problems absent from prisma/tasks.ts (not in AOJ's contest listing). */ + absentLetters: string[]; +} + +type Problem = { id: string; name: string }; + +function official(titles: string[], absentLetters: string[] = []): OfficialContest { + return { titles, absentLetters }; +} + +// Transcribed from the maintainer's spreadsheet (screenshots of 2026-10-24, rows 2-1025). +const OFFICIAL_CONTESTS: Record = { + 'JAGSummer2024-day2': official([ + 'Add Add Add', + 'Broken Parentheses', + 'Convenient Banknotes', + 'Do Make Segment Tree', + 'Expression Sum', + 'Flip Path on Rooted Tree', + 'Give Me a Lot of Triangles', + 'Half Plane Painting', + 'I Love Square Number', + 'Just Believe in Binary Search', + 'K-th Nondivisor', + 'Linear Time Inversion Number', + 'Max Sum of GCD', + 'Noncoprime Subsequences', + ]), + 'JAGSummer2024-day3': official( + [ + 'Tower Defense', + 'Renovation', + 'Commutativity', + 'Paper Cut Game', + 'Ball Passing', + 'Halfway Through the Book', + 'Interactive String Guessing', + 'Coins on a Tree', + 'Fragile Tree', + 'Draw the Tree', + 'Equal or Not Equal', + ], + ['G'], + ), + 'JAGSummer2023-day2': official([ + 'Sum of Product of Binomial Coefficients', + 'Mercurialist', + 'Umbrella Queries', + 'Gemini Tree (Ver.Jadeite)', + 'Gemini Tree (Ver.Lapislazuli)', + 'Tea time in the grand garden', + 'Fraises dans une boite', + 'Empty Quartz', + 'Distance Permutation', + 'Knight Game', + 'Drifting', + 'Disjoint-Sparse-Table Optimization', + ]), + 'JAGSummer2023-day3': official([ + 'Roller Coaster', + 'Break a Prison', + 'Camp room assignment', + 'Many-hued Tree', + 'Gacha 101', + 'Digit-only subrectangles', + 'Convex Polygon MST', + 'LCP Queries', + 'Best parentheses', + 'Edit distance on table', + 'Odd trip plans', + ]), + JAGRegional2022: official([ + 'Stop and Go', + '1-Player Concentration', + 'Track Train Trail', + '2-LIS', + 'Sharp 2-SAT', + 'Seimei Handan 999.0', + 'Avoid bombings', + 'Sloppy city planning', + 'N-1 Truths and a Lie', + 'Most distant point from the stations', + 'Sort Compressed Strings', + 'Tree Automorphism', + ]), + JAGRegional2021: official([ + 'Harvest', + 'Parse the Syntax Tree', + 'Permutation Magic', + 'MFP: Most Fluctuated Player', + "Underground's SUNDAY", + 'qarentheziz zepuence', + 'Pizza delivery', + 'include', + '(N+1)-legged race', + 'Isomorphic?', + 'Zombie Land 2', + ]), + JAGRegional2020: official([ + 'On-Call', + 'Warp Points', + 'Rebound Sequences', + 'Surround the Castle', + 'Buttons', + 'Mountain View', + 'Yet Another Expression Mining', + 'Aggressive Traveller', + 'Irreversible Reactions', + 'X-percent Blooming', + 'Feed candies', + ]), + 'JAGSummer2019-day1': official([ + 'AIGo', + 'Non-trivial Common Divisor', + 'Universal and Existential Quantifiers', + 'Permutation Sort', + 'Consistent Trading', + 'All your base are belong to us', + 'Route Calculator Returns', + 'N-by-M grid calculation', + 'Zombie Land', + 'Rooks Game', + 'Bombing', + ]), + 'JAGSummer2019-day2': official([ + 'Undo Swapping', + 'Spent Fuel Disposal', + 'Cyclic String', + 'Nose And Lung', + 'Expensive Function', + 'Clique Drawing', + 'Human Seperation', + 'Permutation Score', + 'Palindrome Compliment', + 'Prefix Suffix Nim', + ]), + 'JAGSummer2019-day3': official([ + 'Union Ball', + 'AddMulSubDiv', + 'Shuttle Run', + 'XORANDORBAN', + 'Red Black Balloons', + 'Invariant Tree', + 'Toss Cut Tree', + 'Colorful Tree', + 'Add', + 'Horizontal-Vertical Permutation', + ]), + 'JAGSummer2018-day3': official([ + 'GuruGuru', + 'Colorful Drink', + "Santa's Gift", + 'Prefix Suffix Search', + 'Magic Triangles', + 'Nim without Zero', + 'Additions', + 'Enlarge Circles', + 'Sum of QQ', + 'Prime Routing', + 'Rough Sorting', + ]), + JAGRegional2017: official([ + 'Window', + 'Tournament Chart', + 'Prime-Factor Prime', + 'Revenge of the Broken Door', + 'Tree Separator', + 'RPG Maker', + 'Coin Slider', + 'Separate String', + 'Revenge of the Endless BFS', + 'Farm Village', + 'Conveyor Belt', + ]), + 'JAGSummer2017-day3': official([ + 'Star in Parentheses', + 'Slimming Plan', + 'Ninja Map', + 'Janken Master', + 'Route Calculator', + 'Endless BFS', + 'Low Range-Sum Matrix', + 'Tiny Room', + "Librarian's Work", + 'Sum Source Detection', + 'Permutation Period', + ]), + JAGRegional2016: official([ + 'Best Matched Pair', + 'Help the Princess!', + "We don't wanna work!", + 'Parentheses', + 'Similarity of Subtrees', + 'Escape from the Hell', + 'Share the Ruins Preservation', + 'Pipe Fitter and the Fierce Dogs', + 'Multisect', + 'Compressed Formula', + 'Non-redundant Drive', + ]), + JAGRegional2015: official([ + 'M and A', + 'Change a Password', + 'Delete Files', + 'Line Gimmick', + 'Shifting a Matrix', + 'Modern Announce Network', + 'Cube Dividing', + 'Donut Decoration', + 'Shortest Bridge', + 'Longest Shortest Path', + 'Optimal Tournament', + ]), + 'JAGSummer2015-day2': official([ + "Let's Solve Geometric Problems", + 'Jail', + 'ABC Gene', + 'Dark Room', + 'An Equation in a Mine', + 'Nearly Cyclic String', + 'Escape', + 'Bit Operation Game', + 'Twin Reverse', + 'Connection', + 'Leapfrog', + ]), + 'JAGSummer2015-day4': official([ + 'Where is the Boundary', + 'Vector Field', + 'Kuru Kuru Sushi', + 'Identity Function', + 'Enclose Points', + 'Marching Course', + 'Surface Area of Cubes', + 'Laser Cutter', + 'Live Programming', + 'Black Company', + ]), + JAGSpring2015: official([ + 'Balanced Paths', + 'Card Game Strategy', + 'Casino', + 'Content Delivery', + 'Cost Performance Flow', + 'ICPC Teams', + 'JAG-channel II', + 'Kimagure Cleaner', + 'Midpoint', + 'New Game AI', + 'Runner and Sniper', + 'Wall Making Game', + ]), + 'JAGSummer2014-day2': official([ + 'Manhattan', + 'Dictionary', + 'Clique Coloring', + 'Dense Amidakuji', + 'Cellular Automaton', + 'Directions', + 'Snake', + 'Distance Sum', + 'Substring Pairs', + 'Hyperrectangle', + ]), + JAGRegional2014: official([ + 'North North West', + 'Unknown Switches', + 'Speedrun', + 'Flowers', + 'Square in Circles', + 'Reverse a Road II', + 'Cookie Counter', + 'Points and Lines', + 'Color the Map Extreme', + 'Website Tour', + 'Idempotent Filter', + ]), + 'JAGSummer2014-day4': official([ + 'Yamanote Line', + 'Prowler', + 'Magic Bullet', + 'Dinner', + 'AI', + 'Longest Match', + 'Recycle', + 'Tournament', + 'The Capital', + 'Vongress', + ]), + JAGSpring2014: official([ + 'Breadth-First Search by Foxpower', + 'Cube Coloring', + 'Decoding Ancient Messages', + 'LR', + 'Parentheses', + 'Polygon Guards', + 'Proportional Representation', + 'RLE Replacement', + 'Tokyo Olympics Center', + 'Unfair Game', + ]), + JAGRegional2013: official( + [ + 'Broken Audio Signal', + 'Restore Calculation', + 'SIRO Challenge', + 'Everlasting -One-', + 'Putter', + 'Shipura', + 'Floating Islands', + 'Venn Diagram', + 'Overwriting Game', + 'Magical Switches', + ], + ['A', 'B', 'C'], + ), + 'JAGSummer2013-day2': official([ + 'Koto Distance', + 'Evacuation Route', + 'Apples', + 'TiMe Table', + 'Pattern Language', + 'Social Monsters', + 'Perm Query', + ]), + 'JAGSummer2013-day3': official([ + 'Invest Master', + 'Minus One', + 'Wave Attack', + 'Fast Division', + 'Ordering', + 'Phutball', + 'Unordered Operators', + 'Almost Same Substring', + 'A + B', + 'KuruKuruKururin', + 'Air Pollution', + 'Trip to Kyoto', + ]), + 'JAGSummer2013-day4': official([ + 'Thread Tree', + 'Trodden Cable', + 'Fox Observation', + 'Removing Magical Tiles', + 'Optimal alpha beta pruning', + 'Graph Automata Player', + 'Spotlight Movement', + 'Gravity Point', + 'Multi Path Story', + 'Rotation Game', + ]), + JAGSpring2013: official([ + 'Everlasting Zero', + 'Integer in Integer', + 'Iyasugigappa', + 'Medical Inspection', + 'Minimum Spanning Tree', + 'Point Distance', + 'Revenge of Minimum Cost Flow', + 'Rings', + 'The J-th Number', + 'Tree Reconstruction', + ]), + JAGRegional2012: official([ + 'Dictionary', + "Texas hold 'em", + 'Median Tree', + 'Billiard', + 'Stack Maze', + "Counting 1's", + 'Ancient Commemorative Monolith', + 'Magical Bridges', + 'Hashigo Sama', + 'Ancient Scrolls', + ]), + 'JAGSummer2012-day2': official([ + 'Kakezan', + 'A Holiday of Miss Brute Force', + 'Treasure Hunt', + 'hosonagaitokoro', + 'Lost Number', + 'marukaite', + 'Longest Increasing Sequence', + 'House Moving', + 'Sports Days 2.0', + 'Final Defense Line', + ]), + 'JAGSummer2012-day3A': official([ + 'Audition', + 'Zero Division Checker', + 'Card', + 'DNA', + 'YAML', + 'Hakone', + ]), + 'JAGSummer2012-day3B': official([ + 'Kagisys', + 'FizzBuzz', + 'ConvexCut', + 'ReverseSort', + 'Substring', + 'MinimumCostPath', + 'Enumeration', + ]), + 'JAGSummer2012-day4': official([ + 'A Two Floors Dungeon', + 'Area Folding', + 'Connect', + 'Do use segment tree', + 'Move on Dice', + 'Pipeline Plans', + 'Presentation', + 'Repairing', + 'Sun and Moon', + 'Usoperanto', + ]), + JAGSpring2012: official([ + 'Asynchronous Exceptions', + 'AYBABTU', + 'Billiards Sorting', + 'Digit', + 'Dungeon Creation', + 'Longest Lane', + 'Play in Basic', + 'Skyland', + 'Three-way Branch', + 'Tree Allocation', + ]), + JAGWinter2011: official([ + "Carpenters' Language", + 'Kth Sentence', + 'Light Road', + 'Matrix Operation', + 'Multi Ending Story', + 'Network Reliability', + 'Runaway Domino', + 'Sunny Graph', + 'Testing Circuits', + 'World Trip', + ]), + JAGRegional2011: official([ + 'Infinity Maze', + 'Butterfly', + 'Chinese Classics', + 'Revenge of Champernowne Constant', + 'Full Text Search', + 'Mysterious Maze', + 'Number Sorting', + 'Sky Jump', + 'Mobile Network', + 'Blue Forest', + ]), + 'JAGSummer2011-day2': official([ + 'Attack the Moles', + 'Brilliant Stars', + 'Common Palindromes', + 'Dangerous Tower', + 'Entangled with Lottery', + 'Power of Power', + 'Quest of Merchant', + 'Rectangular Stamps', + 'Starting Line', + 'Tiles are Colorful', + ]), + 'JAGSummer2011-day3': official([ + 'Calender Colors', + 'Sleeping Time', + 'On or Off', + 'Marathon Match', + 'Reverse Roads', + 'Beautiful Currency', + 'Rabbit Party', + 'Palindrome Generator', + 'White Bird', + 'Vector Compression', + ]), + 'JAGSummer2011-day4': official([ + 'Rose Garden Witch', + 'Dessert Witch', + 'Magical Girl Sayaka-chan', + 'Box Witch', + 'Scribbling witch', + 'Shadow Witch', + 'Mermaid Witch', + 'Class Representative Witch', + 'Set-constructing Witch', + 'Soul Gem Game', + ]), + 'JAGWinter2010-day3': official([ + 'CatChecker', + 'RabbitWalking', + 'TransferTrain', + 'IkaNumber', + 'HullMarathon', + 'RabbitLunch', + 'CarrotBreeding', + 'DisconnectedGame', + 'ThreeRooks', + 'SolveMe', + ]), + 'JAGWinter2010-day4': official([ + 'Bicube', + 'Bubble Puzzle', + 'Fair Game', + 'King Slime', + 'Rabbit Game Playing', + 'Rabbit Jumping', + 'Shelter', + 'Sightseeing Tour', + 'Tampopo Machine', + 'Top of the Hill', + ]), + JAGRegional2010: official([ + 'Era Name', + 'Step Step Evolution', + 'Dungeon Quest II', + 'Dice Room', + 'Alice and Bomb', + 'Two-Wheel Buggy', + 'Camera Control', + 'Road Construction', + 'Operator', + 'Merry Christmas', + ]), + 'JAGSummer2010-day2': official([ + 'Earth Invasion Diary of Miyabi-sensei', + 'A Way to Invite Friends', + 'Space-Time Sugoroku Road', + 'My friends are small', + 'Roads on Towns', + '10-Year-Old Dynamic Programming', + 'Spring Tiles', + 'Remodeling Plan for Neko-Nabe (tentative)', + 'Intelligible Double Magic', + "Person responsible for problem description don't w…", + ]), + 'JAGSummer2010-day3': official([ + 'Ennichi', + 'Carrot Tour', + 'Usagitobi', + 'Graph Construction', + 'Rabbit Plays Games!', + 'The Castle', + 'Nurie', + 'Nearest Station', + 'Lapin Noir', + 'Usaneko Matrix', + ]), + 'JAGSummer2010-day4': official([ + "Alien's Counting", + 'Kaeru Jump', + 'Save your cats', + 'Dungeon Wall', + 'Psychic Accelerator', + 'Bouldering', + "DON'T PANIC!", + 'Ropeway', + 'How to Create a Good Game', + 'Cruel Bingo', + ]), + JAGWinter2009: official([ + 'Approximate Circle', + 'Blame Game', + 'Earn Big', + 'Exportation in Space', + 'Laser Puzzle', + 'Magnum Tornado', + "Nezumi's Treasure", + 'Testing Sorting Networks', + 'Train King', + 'Tree Construction', + ]), + JAGRegional2009: official([ + 'Whist', + 'For the Peace', + 'Champernowne Constant', + 'Futon', + 'Safe Area', + 'Water Tank', + "Neko's Treasure", + 'Eleven Lover', + 'Crystal Jails', + 'Cave Explorer', + ]), + 'JAGSummer2009-day2': official([ + 'Infected Computer', + 'Magic Slayer', + 'Dial Lock', + 'Double Sorting', + 'Symmetry', + 'Voronoi Island', + 'Defend the Bases', + 'Galaxy Wide Web Service', + 'Tatami', + 'Revenge of the Round Table', + ]), + 'JAGSummer2009-day3': official([ + 'Strange String Manipulation', + 'Erratic Sleep Habits', + 'Find the Point', + "Luigi's Tavern", + 'Colored Octahedra', + 'Marked Ancestor', + 'Strange Couple', + "Queen's Case", + 'Wind Passages', + 'Secret Operation', + ]), + 'JAGWinter2008-day2': official([ + 'Adhoc Translation', + 'Artistic Art Museum', + 'Complex Integer Solutions', + 'Dial Key', + 'Dungeon Master', + 'Exciting Bicycle', + 'Fuel Problem', + 'Magic Walls', + 'Sort by Hand', + 'Substring Expression', + 'Up Above the World So High', + ]), + 'JAGWinter2008-day3': official([ + 'Cache Control', + 'Cover Time', + 'Craftsman', + 'Divide the Water', + "Mickle's Beam", + 'Poor Computer', + 'Riffle Swap', + 'Round Table', + 'Strange Currency System', + 'Tangram', + 'Trading Ship', + ]), + JAGRegional2008: official( + [ + 'Everlasting...?', + 'Headstrong Student', + 'Dig or Climb', + 'Rotation Estimation', + 'Optimal Rest', + 'Controlled Tournament', + 'Entangled Tree', + 'Ramen Shop', + "Cousin's Aunt", + 'Colony Maintenance', + ], + ['J'], + ), + 'JAGSummer2008-day2': official([ + 'Pi is Three', + 'Left Hand Rule', + 'Alice and Bob', + 'Deadly Dice Game', + 'Reverse a Road', + 'Webby Subway', + 'Time Trial', + 'Vending Machine', + 'Memory Match', + 'Tile Puzzle', + ]), + 'JAGSummer2008-day4': official([ + "Girls' Party", + 'Bitwise Kingdom', + 'Adaptive Time Slicing Quantization', + 'Reaction', + 'Magical Island', + 'Ninja Legend', + 'Do It', + 'Robot Communication', + ]), + 'JAGWinter2007-day2': official([ + 'Defend the Nation', + 'Electrophoretic', + 'Median Filter', + 'Life Game', + 'Subdividing a Land', + 'Connect Line Segments', + 'Oil Company', + 'Finding the Top RPS Player', + 'Revenge of Voronoi', + 'Castle Wall', + ]), + 'JAGWinter2007-day3': official( + [ + 'Optimization of Combinatorial Circuits', + 'Divisor Function', + 'Magical Dungeon', + 'Land Mark', + 'Networking Company', + 'Alien Pianist', + 'Japanese Style Pub', + 'Two Rings', + 'Text Justification', + 'Billion Million Thousand', + ], + ['E'], + ), + JAGRegional2007: official([ + 'Space Coconut Crab II', + 'Sort the Panels', + 'Disarmament of the Units', + 'So Sleepy', + 'Alice in Foxland', + 'Lying about Your Age', + 'Turn Polygons', + "Robots' Crash", + 'Linear Ether Geometry', + ]), + 'JAGSummer2007-day1': official([ + 'Dance Dance Revolution', + 'Compress Files', + 'Save the Energy', + 'Goofy Converter', + 'Black Force', + ]), + 'JAGSummer2007-day2': official([ + 'Hit and Blow', + 'Turn Left', + '!', + 'Speed', + 'Spirograph', + 'Mysterious Dungeons', + 'Repeated Subsequences', + 'Petoris', + 'Pythagoraslope', + ]), + 'JAGSummer2007-day3': official([ + 'Election', + 'Jaggie Spheres', + 'Nagashi Soumen', + 'Private Teacher', + 'Triangles', + 'Two-finger Programming', + 'Walk under a Scorching Sun', + ]), + 'JAGWinter2006-day2': official( + [ + 'Cells', + 'Japan', + 'Maximum', + 'The Phantom', + 'Payment System', + 'Rakunarok', + 'Roof', + 'Seminar Room', + 'String Compression', + ], + ['A', 'B', 'C', 'E', 'G', 'H', 'I'], + ), + 'JAGWinter2006-day3': official( + [ + 'Dock to the Future', + 'Widget Factory', + 'Hills', + 'Flame of Nucleus', + 'Safe Packing', + 'The "Shortest" Pair of Paths', + 'Resource', + 'Exact Arithmetic', + 'Counting Triangles', + 'Wireing Assistant', + ], + ['B', 'C', 'E', 'F', 'I', 'J'], + ), + JAGRegional2006: official([ + 'Ruins', + 'Hyper Rock-Scissors-Paper', + 'Online Quiz System', + 'Rock Man', + 'Autocorrelation Function', + 'It Prefokery Pio', + 'Traffic', + 'Restriction Enzyme', + 'The Extreme Slalom', + ]), + 'JAGSummer2006-day1': official( + ['Monkey Business', 'The Closest Circle', 'Sort that Queue'], + ['A', 'C'], + ), + 'JAGSummer2006-day2': official([ + 'Moduic Squares', + 'Restaurant', + 'Tetrahedra', + 'International Party', + 'Hide-and-seek', + 'TV Watching', + 'Make Friendships', + 'Slippy Floors', + 'Roads in a City', + ]), + 'JAGSummer2006-day3': official( + [ + 'Rolling Cubes', + 'Reading Brackets in English', + 'Grated Radish', + 'Greedy, Greedy.', + 'First Experience', + 'Web 0.5', + "Philosopher's Stone", + ], + ['A'], + ), + JAGRegional2005: official( + [ + 'Blackjack', + 'Eight Princes', + 'Geometry?! Why Not??', + 'Divisor is the Conqueror', + 'Reading a Chord', + 'University Rankings', + 'Gather on the Clock', + 'Tiling Polygons', + 'Light The Room', + ], + ['C', 'F', 'H'], + ), +}; + +// Ignore case, spacing, and punctuation, which differ between the spreadsheet and AOJ. +function normalizeTitle(title: string): string { + return title.toLowerCase().replace(/[^a-z0-9]/g, ''); +} + +function matchesTitle(aojTitle: string, officialTitle: string): boolean { + const normalizedAojTitle = normalizeTitle(aojTitle); + const normalizedOfficialTitle = normalizeTitle(officialTitle); + + if (officialTitle.endsWith(TRUNCATION_MARK)) { + return normalizedAojTitle.startsWith(normalizedOfficialTitle); + } + + return normalizedAojTitle === normalizedOfficialTitle; +} + +function toLetter(position: number): string { + return String.fromCharCode(65 + position); +} + +function groupTargetProblems(): Map { + const problemsByContest = new Map(); + + for (const task of tasks) { + if (!TARGET_CONTEST_ID.test(task.contest_id)) { + continue; + } + + const problems = problemsByContest.get(task.contest_id) ?? []; + problems.push({ id: task.id, name: task.name }); + problemsByContest.set(task.contest_id, problems); + } + + for (const problems of problemsByContest.values()) { + problems.sort((first, second) => Number(first.id) - Number(second.id)); + } + + return problemsByContest; +} + +// Letters of the problems that AOJ lists, in order; absent ones are skipped, not shifted into. +function getPresentLetters({ titles, absentLetters }: OfficialContest): string[] { + return titles + .map((_title, position) => toLetter(position)) + .filter((letter) => !absentLetters.includes(letter)); +} + +function verifyContest(contestId: string, problems: Problem[]): string[] { + const officialContest = OFFICIAL_CONTESTS[contestId]; + + if (officialContest === undefined) { + return [`${contestId}: not in the official list`]; + } + + const presentLetters = getPresentLetters(officialContest); + const errors: string[] = []; + + if (problems.length !== presentLetters.length) { + errors.push( + `${contestId}: AOJ has ${problems.length} problems, expected ${presentLetters.length}`, + ); + } + + problems.forEach((problem, position) => { + const letter = presentLetters[position]; + const officialTitle = + letter === undefined ? undefined : officialContest.titles[letter.charCodeAt(0) - 65]; + + if (officialTitle === undefined || !matchesTitle(problem.name, officialTitle)) { + errors.push( + `${contestId} ${letter ?? '?'}: AOJ has "${problem.name}", expected "${officialTitle ?? '(none)'}"`, + ); + } + }); + + return errors; +} + +function main(): void { + const problemsByContest = groupTargetProblems(); + const taskCount = [...problemsByContest.values()].reduce( + (sum, problems) => sum + problems.length, + 0, + ); + + if (problemsByContest.size !== EXPECTED_CONTEST_COUNT || taskCount !== EXPECTED_TASK_COUNT) { + throw new Error( + `Extracted ${problemsByContest.size} contests / ${taskCount} tasks, expected ${EXPECTED_CONTEST_COUNT} / ${EXPECTED_TASK_COUNT}`, + ); + } + + const errors = [...problemsByContest].flatMap(([contestId, problems]) => + verifyContest(contestId, problems), + ); + const unknownContests = Object.keys(OFFICIAL_CONTESTS).filter( + (contestId) => !problemsByContest.has(contestId), + ); + + errors.push(...unknownContests.map((contestId) => `${contestId}: not in prisma/tasks.ts`)); + + console.error(`Contests: ${problemsByContest.size}, tasks: ${taskCount}`); + + if (errors.length > 0) { + errors.forEach((error) => console.error(`ERROR ${error}`)); + process.exitCode = 1; + + return; + } + + console.error(`All ${taskCount} titles match the official letters`); + + const letters: Record = {}; + const overrides: [string, string, string][] = []; + + for (const [contestId, problems] of problemsByContest) { + const presentLetters = getPresentLetters(OFFICIAL_CONTESTS[contestId]); + const shifted = problems.some( + (_problem, position) => presentLetters[position] !== toLetter(position), + ); + + problems.forEach((problem, position) => { + letters[problem.id] = presentLetters[position]; + + // Every row of a shifted contest is overridden, so the migration does not mix two rules. + if (shifted) { + overrides.push([contestId, problem.id, presentLetters[position]]); + } + }); + } + + console.error( + `Contests that differ from the basic rule: ${new Set(overrides.map(([contestId]) => contestId)).size} / ${overrides.length} rows`, + ); + console.log(JSON.stringify({ letters, overrides }, null, 2)); +} + +main(); From 4c7b9763f9b543d3a83bb4f4d3d9656fe72fa2c8 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 00:10:38 +0000 Subject: [PATCH 4/8] feat: store problem letters as task_table_index for AOJ JAG camps and Regional Extend the PR1 problem letters to the JAG training camps (Summer / Winter / Spring, 50 contests / 467 tasks) and JAG Regional (16 contests / 158 tasks): - Seed: add JAG Regional from production, set letters for all 625 tasks, and drop the legacy ". " prefix from the training camp titles. - Migration: apply the basic rule, overwrite the 8 contests whose letters shift because problems are missing from AOJ (45 rows, from the maintainer's problem list), and strip the title prefixes. Each step checks its row count (0 / camps only / camps + Regional) and raises otherwise, which rolls back the whole migration. - Import: assign letters for every JAG round, not only Prelim. Verified on the local DB: a SELECT preview matched the cross-check script for all 625 rows; after applying, 625/625 rows match the preview and other AOJ types are untouched. Mutants (JAG letters only for Prelim, only for ICPC, a corrected letter reverted, A/B swapped, title prefix restored, Regional letter left as id) are each caught. Refs #4030 Co-Authored-By: Claude Opus 5.5 --- .agents/skills/add-tasks/instructions.md | 2 +- .../migration.sql | 109 + prisma/tasks.ts | 2980 +++++++++++------ .../clients/aizu_online_judge/clients.test.ts | 22 +- src/lib/clients/aizu_online_judge/clients.ts | 6 +- .../seed_problem_letters.test.ts | 94 +- 6 files changed, 2254 insertions(+), 959 deletions(-) create mode 100644 prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql diff --git a/.agents/skills/add-tasks/instructions.md b/.agents/skills/add-tasks/instructions.md index e9c8f6191..eaf319cbb 100644 --- a/.agents/skills/add-tasks/instructions.md +++ b/.agents/skills/add-tasks/instructions.md @@ -17,7 +17,7 @@ - Source: `https://kenkoooo.com/atcoder/resources/problems.json` - atcoder.jp is unreachable and WebFetch truncates problems.json, so a miss there is not evidence of absence — ask the user to paste the rows - Use `id` / `problem_index` / `name` / `title` verbatim; do not derive `id` from contest_id -- ICPC Prelim / ICPC Regional / JAG Prelim: set `problem_index` to the problem letter, not the AOJ problem id — A, B, C... in ascending problem id within the contest, as `assignProblemLetters` does on import. `seed_problem_letters.test.ts` checks this +- AOJ ICPC and every AOJ JAG contest (Prelim, Regional, training camps): set `problem_index` to the problem letter, not the AOJ problem id — A, B, C... in ascending problem id within the contest, as `assignProblemLetters` does on import. When a problem before the last one is missing from AOJ, take the letters from the maintainer's problem list and add the contest to `LETTERS_OF_SHIFTED_CONTESTS`. Keep `title` plain, without a `. ` prefix. `seed_problem_letters.test.ts` checks both ## Tests diff --git a/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql b/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql new file mode 100644 index 000000000..63791c23b --- /dev/null +++ b/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql @@ -0,0 +1,109 @@ +-- Replace the AOJ problem id in task_table_index with the problem letter (A, B, C...) +-- for the JAG training camps (Summer / Winter / Spring) and JAG Regional, and strip the +-- legacy ". " prefix from the training camp titles. +-- ICPC and JAG Prelim were converted in 20261024033928_update_aoj_task_table_index. +-- Each step checks its row count and raises, rolling back the whole migration, when the +-- count is unexpected: 0 for an empty database (prisma migrate reset), the training camps +-- only for a seeded database, and the camps plus all 16 JAG Regional contests elsewhere. +DO $$ +DECLARE + updated_count integer; +BEGIN + -- (1) Basic rule: letters follow ascending problem id within each contest. + WITH letters AS ( + SELECT task_id, + chr(64 + row_number() OVER ( + PARTITION BY contest_id + ORDER BY task_id::int + )::int) AS letter + FROM "task" + WHERE contest_type = 'AOJ_JAG' + AND contest_id ~ '^JAG(Summer|Winter|Spring|Regional)' + -- Rows that already hold a letter (added later with letters) are left as they are. + AND task_table_index = task_id + ) + UPDATE "task" AS t + SET "task_table_index" = letters.letter + FROM letters + WHERE t."task_id" = letters.task_id + -- Redundant with the CTE filter, but keeps the blast radius fixed. + AND t."contest_type" = 'AOJ_JAG' + AND t."contest_id" ~ '^JAG(Summer|Winter|Spring|Regional)'; + + GET DIAGNOSTICS updated_count = ROW_COUNT; + IF updated_count NOT IN (0, 467, 625) THEN + RAISE EXCEPTION 'basic rule: updated % rows, expected 0, 467 or 625. Import every JAG Regional contest from the admin page, or none, before migrating.', updated_count; + END IF; + + -- (2) Contests with problems missing from AOJ: the basic rule shifts their letters, so every + -- row takes its letter from the maintainer's problem list instead. + UPDATE "task" AS t + SET "task_table_index" = fixes.letter + FROM (VALUES + ('JAGRegional2005', '2024', 'A'), + ('JAGRegional2005', '2025', 'B'), + ('JAGRegional2005', '2026', 'D'), + ('JAGRegional2005', '2027', 'E'), + ('JAGRegional2005', '2028', 'G'), + ('JAGRegional2005', '2029', 'I'), + ('JAGRegional2013', '2568', 'D'), + ('JAGRegional2013', '2569', 'E'), + ('JAGRegional2013', '2570', 'F'), + ('JAGRegional2013', '2571', 'G'), + ('JAGRegional2013', '2572', 'H'), + ('JAGRegional2013', '2573', 'I'), + ('JAGRegional2013', '2574', 'J'), + ('JAGSummer2006-day1', '2057', 'B'), + ('JAGSummer2006-day3', '2067', 'B'), + ('JAGSummer2006-day3', '2068', 'C'), + ('JAGSummer2006-day3', '2069', 'D'), + ('JAGSummer2006-day3', '2070', 'E'), + ('JAGSummer2006-day3', '2071', 'F'), + ('JAGSummer2006-day3', '2072', 'G'), + ('JAGSummer2024-day3', '3637', 'A'), + ('JAGSummer2024-day3', '3638', 'B'), + ('JAGSummer2024-day3', '3639', 'C'), + ('JAGSummer2024-day3', '3640', 'D'), + ('JAGSummer2024-day3', '3641', 'E'), + ('JAGSummer2024-day3', '3642', 'F'), + ('JAGSummer2024-day3', '3644', 'H'), + ('JAGSummer2024-day3', '3645', 'I'), + ('JAGSummer2024-day3', '3646', 'J'), + ('JAGSummer2024-day3', '3647', 'K'), + ('JAGWinter2006-day2', '2073', 'D'), + ('JAGWinter2006-day2', '2074', 'F'), + ('JAGWinter2006-day3', '2075', 'A'), + ('JAGWinter2006-day3', '2076', 'D'), + ('JAGWinter2006-day3', '2077', 'G'), + ('JAGWinter2006-day3', '2078', 'H'), + ('JAGWinter2007-day3', '2122', 'A'), + ('JAGWinter2007-day3', '2123', 'B'), + ('JAGWinter2007-day3', '2124', 'C'), + ('JAGWinter2007-day3', '2125', 'D'), + ('JAGWinter2007-day3', '2126', 'F'), + ('JAGWinter2007-day3', '2127', 'G'), + ('JAGWinter2007-day3', '2128', 'H'), + ('JAGWinter2007-day3', '2129', 'I'), + ('JAGWinter2007-day3', '2130', 'J') + ) AS fixes (contest_id, task_id, letter) + WHERE t."task_id" = fixes.task_id + AND t."contest_id" = fixes.contest_id + AND t."contest_type" = 'AOJ_JAG'; + + GET DIAGNOSTICS updated_count = ROW_COUNT; + IF updated_count NOT IN (0, 32, 45) THEN + RAISE EXCEPTION 'letter overrides: updated % rows, expected 0, 32 or 45', updated_count; + END IF; + + -- (3) Strip the legacy ". " prefix from the training camp titles. + UPDATE "task" + SET "title" = substr("title", length("task_id") + 3) + WHERE "contest_type" = 'AOJ_JAG' + AND "contest_id" ~ '^JAG(Summer|Winter|Spring|Regional)' + AND starts_with("title", "task_id" || '. '); + + GET DIAGNOSTICS updated_count = ROW_COUNT; + IF updated_count NOT IN (0, 467) THEN + RAISE EXCEPTION 'titles: updated % rows, expected 0 or 467', updated_count; + END IF; +END $$; diff --git a/prisma/tasks.ts b/prisma/tasks.ts index 647ea6676..09de7f3ca 100755 --- a/prisma/tasks.ts +++ b/prisma/tasks.ts @@ -14395,3274 +14395,4386 @@ export const tasks = [ name: "Let's Build an Evil Robot Army", title: "Let's Build an Evil Robot Army", }, + { + id: '2024', + contest_id: 'JAGRegional2005', + problem_index: 'A', + name: 'Blackjack', + title: 'Blackjack', + }, + { + id: '2025', + contest_id: 'JAGRegional2005', + problem_index: 'B', + name: 'Eight Princes', + title: 'Eight Princes', + }, + { + id: '2026', + contest_id: 'JAGRegional2005', + problem_index: 'D', + name: 'Divisor is the Conqueror', + title: 'Divisor is the Conqueror', + }, + { + id: '2027', + contest_id: 'JAGRegional2005', + problem_index: 'E', + name: 'Reading a Chord', + title: 'Reading a Chord', + }, + { + id: '2028', + contest_id: 'JAGRegional2005', + problem_index: 'G', + name: 'Gather on the Clock', + title: 'Gather on the Clock', + }, + { + id: '2029', + contest_id: 'JAGRegional2005', + problem_index: 'I', + name: 'Light The Room', + title: 'Light The Room', + }, + { + id: '2030', + contest_id: 'JAGRegional2006', + problem_index: 'A', + name: 'Ruins', + title: 'Ruins', + }, + { + id: '2031', + contest_id: 'JAGRegional2006', + problem_index: 'B', + name: 'Hyper Rock-Scissors-Paper', + title: 'Hyper Rock-Scissors-Paper', + }, + { + id: '2032', + contest_id: 'JAGRegional2006', + problem_index: 'C', + name: 'Online Quiz System', + title: 'Online Quiz System', + }, + { + id: '2033', + contest_id: 'JAGRegional2006', + problem_index: 'D', + name: 'Rock Man', + title: 'Rock Man', + }, + { + id: '2034', + contest_id: 'JAGRegional2006', + problem_index: 'E', + name: 'Autocorrelation Function', + title: 'Autocorrelation Function', + }, + { + id: '2035', + contest_id: 'JAGRegional2006', + problem_index: 'F', + name: 'It Prefokery Pio', + title: 'It Prefokery Pio', + }, + { + id: '2036', + contest_id: 'JAGRegional2006', + problem_index: 'G', + name: 'Traffic', + title: 'Traffic', + }, + { + id: '2037', + contest_id: 'JAGRegional2006', + problem_index: 'H', + name: 'Restriction Enzyme', + title: 'Restriction Enzyme', + }, + { + id: '2038', + contest_id: 'JAGRegional2006', + problem_index: 'I', + name: 'The Extreme Slalom', + title: 'The Extreme Slalom', + }, + { + id: '2039', + contest_id: 'JAGRegional2007', + problem_index: 'A', + name: 'Space Coconut Crab II', + title: 'Space Coconut Crab II', + }, + { + id: '2040', + contest_id: 'JAGRegional2007', + problem_index: 'B', + name: 'Sort the Panels', + title: 'Sort the Panels', + }, + { + id: '2041', + contest_id: 'JAGRegional2007', + problem_index: 'C', + name: 'Disarmament of the Units', + title: 'Disarmament of the Units', + }, + { + id: '2042', + contest_id: 'JAGRegional2007', + problem_index: 'D', + name: 'So Sleepy', + title: 'So Sleepy', + }, + { + id: '2043', + contest_id: 'JAGRegional2007', + problem_index: 'E', + name: 'Alice in Foxland', + title: 'Alice in Foxland', + }, + { + id: '2044', + contest_id: 'JAGRegional2007', + problem_index: 'F', + name: 'Lying about Your Age', + title: 'Lying about Your Age', + }, + { + id: '2045', + contest_id: 'JAGRegional2007', + problem_index: 'G', + name: 'Turn Polygons', + title: 'Turn Polygons', + }, + { + id: '2046', + contest_id: 'JAGRegional2007', + problem_index: 'H', + name: "Robots' Crash", + title: "Robots' Crash", + }, + { + id: '2047', + contest_id: 'JAGRegional2007', + problem_index: 'I', + name: 'Linear Ether Geometry', + title: 'Linear Ether Geometry', + }, + { + id: '2048', + contest_id: 'JAGRegional2008', + problem_index: 'A', + name: 'Everlasting...?', + title: 'Everlasting...?', + }, + { + id: '2049', + contest_id: 'JAGRegional2008', + problem_index: 'B', + name: 'Headstrong Student', + title: 'Headstrong Student', + }, + { + id: '2050', + contest_id: 'JAGRegional2008', + problem_index: 'C', + name: 'Dig or Climb', + title: 'Dig or Climb', + }, + { + id: '2051', + contest_id: 'JAGRegional2008', + problem_index: 'D', + name: 'Rotation Estimation', + title: 'Rotation Estimation', + }, + { + id: '2052', + contest_id: 'JAGRegional2008', + problem_index: 'E', + name: 'Optimal Rest', + title: 'Optimal Rest', + }, + { + id: '2053', + contest_id: 'JAGRegional2008', + problem_index: 'F', + name: 'Controlled Tournament', + title: 'Controlled Tournament', + }, + { + id: '2054', + contest_id: 'JAGRegional2008', + problem_index: 'G', + name: 'Entangled Tree', + title: 'Entangled Tree', + }, + { + id: '2055', + contest_id: 'JAGRegional2008', + problem_index: 'H', + name: 'Ramen Shop', + title: 'Ramen Shop', + }, + { + id: '2056', + contest_id: 'JAGRegional2008', + problem_index: 'I', + name: "Cousin's Aunt", + title: "Cousin's Aunt", + }, + { + id: '2175', + contest_id: 'JAGRegional2009', + problem_index: 'A', + name: 'Whist', + title: 'Whist', + }, + { + id: '2176', + contest_id: 'JAGRegional2009', + problem_index: 'B', + name: 'For the Peace', + title: 'For the Peace', + }, + { + id: '2177', + contest_id: 'JAGRegional2009', + problem_index: 'C', + name: 'Champernowne Constant', + title: 'Champernowne Constant', + }, + { + id: '2178', + contest_id: 'JAGRegional2009', + problem_index: 'D', + name: 'Futon', + title: 'Futon', + }, + { + id: '2179', + contest_id: 'JAGRegional2009', + problem_index: 'E', + name: 'Safe Area', + title: 'Safe Area', + }, + { + id: '2180', + contest_id: 'JAGRegional2009', + problem_index: 'F', + name: 'Water Tank', + title: 'Water Tank', + }, + { + id: '2181', + contest_id: 'JAGRegional2009', + problem_index: 'G', + name: "Neko's Treasure", + title: "Neko's Treasure", + }, + { + id: '2182', + contest_id: 'JAGRegional2009', + problem_index: 'H', + name: 'Eleven Lover', + title: 'Eleven Lover', + }, + { + id: '2183', + contest_id: 'JAGRegional2009', + problem_index: 'I', + name: 'Crystal Jails', + title: 'Crystal Jails', + }, + { + id: '2184', + contest_id: 'JAGRegional2009', + problem_index: 'J', + name: 'Cave Explorer', + title: 'Cave Explorer', + }, + { + id: '2242', + contest_id: 'JAGRegional2010', + problem_index: 'A', + name: 'Era Name', + title: 'Era Name', + }, + { + id: '2243', + contest_id: 'JAGRegional2010', + problem_index: 'B', + name: 'Step Step Evolution', + title: 'Step Step Evolution', + }, + { + id: '2244', + contest_id: 'JAGRegional2010', + problem_index: 'C', + name: 'Dungeon Quest II', + title: 'Dungeon Quest II', + }, + { + id: '2245', + contest_id: 'JAGRegional2010', + problem_index: 'D', + name: 'Dice Room', + title: 'Dice Room', + }, + { + id: '2246', + contest_id: 'JAGRegional2010', + problem_index: 'E', + name: 'Alice and Bomb', + title: 'Alice and Bomb', + }, + { + id: '2247', + contest_id: 'JAGRegional2010', + problem_index: 'F', + name: 'Two-Wheel Buggy', + title: 'Two-Wheel Buggy', + }, + { + id: '2248', + contest_id: 'JAGRegional2010', + problem_index: 'G', + name: 'Camera Control', + title: 'Camera Control', + }, + { + id: '2249', + contest_id: 'JAGRegional2010', + problem_index: 'H', + name: 'Road Construction', + title: 'Road Construction', + }, + { + id: '2250', + contest_id: 'JAGRegional2010', + problem_index: 'I', + name: 'Operator', + title: 'Operator', + }, + { + id: '2251', + contest_id: 'JAGRegional2010', + problem_index: 'J', + name: 'Merry Christmas', + title: 'Merry Christmas', + grade: 'D3', + }, + { + id: '2320', + contest_id: 'JAGRegional2011', + problem_index: 'A', + name: 'Infinity Maze', + title: 'Infinity Maze', + }, + { + id: '2321', + contest_id: 'JAGRegional2011', + problem_index: 'B', + name: 'Butterfly', + title: 'Butterfly', + }, + { + id: '2322', + contest_id: 'JAGRegional2011', + problem_index: 'C', + name: 'Chinese Classics', + title: 'Chinese Classics', + }, + { + id: '2323', + contest_id: 'JAGRegional2011', + problem_index: 'D', + name: 'Revenge of Champernowne Constant', + title: 'Revenge of Champernowne Constant', + }, + { + id: '2324', + contest_id: 'JAGRegional2011', + problem_index: 'E', + name: 'Full Text Search', + title: 'Full Text Search', + }, + { + id: '2325', + contest_id: 'JAGRegional2011', + problem_index: 'F', + name: 'Mysterious Maze', + title: 'Mysterious Maze', + }, + { + id: '2326', + contest_id: 'JAGRegional2011', + problem_index: 'G', + name: 'Number Sorting', + title: 'Number Sorting', + }, + { + id: '2327', + contest_id: 'JAGRegional2011', + problem_index: 'H', + name: 'Sky Jump', + title: 'Sky Jump', + }, + { + id: '2328', + contest_id: 'JAGRegional2011', + problem_index: 'I', + name: 'Mobile Network', + title: 'Mobile Network', + }, + { + id: '2329', + contest_id: 'JAGRegional2011', + problem_index: 'J', + name: 'Blue Forest', + title: 'Blue Forest', + }, + { + id: '2534', + contest_id: 'JAGRegional2012', + problem_index: 'A', + name: 'Dictionary', + title: 'Dictionary', + }, + { + id: '2535', + contest_id: 'JAGRegional2012', + problem_index: 'B', + name: "Texas hold 'em", + title: "Texas hold 'em", + }, + { + id: '2536', + contest_id: 'JAGRegional2012', + problem_index: 'C', + name: 'Median Tree', + title: 'Median Tree', + }, + { + id: '2537', + contest_id: 'JAGRegional2012', + problem_index: 'D', + name: 'Billiard', + title: 'Billiard', + }, + { + id: '2538', + contest_id: 'JAGRegional2012', + problem_index: 'E', + name: 'Stack Maze', + title: 'Stack Maze', + }, + { + id: '2539', + contest_id: 'JAGRegional2012', + problem_index: 'F', + name: "Counting 1's", + title: "Counting 1's", + }, + { + id: '2540', + contest_id: 'JAGRegional2012', + problem_index: 'G', + name: 'Ancient Commemorative Monolith', + title: 'Ancient Commemorative Monolith', + }, + { + id: '2541', + contest_id: 'JAGRegional2012', + problem_index: 'H', + name: 'Magical Bridges', + title: 'Magical Bridges', + }, + { + id: '2542', + contest_id: 'JAGRegional2012', + problem_index: 'I', + name: 'Hashigo Sama', + title: 'Hashigo Sama', + }, + { + id: '2543', + contest_id: 'JAGRegional2012', + problem_index: 'J', + name: 'Ancient Scrolls', + title: 'Ancient Scrolls', + }, + { + id: '2568', + contest_id: 'JAGRegional2013', + problem_index: 'D', + name: 'Everlasting -One-', + title: 'Everlasting -One-', + }, + { + id: '2569', + contest_id: 'JAGRegional2013', + problem_index: 'E', + name: 'Putter', + title: 'Putter', + }, + { + id: '2570', + contest_id: 'JAGRegional2013', + problem_index: 'F', + name: 'Shipura', + title: 'Shipura', + }, + { + id: '2571', + contest_id: 'JAGRegional2013', + problem_index: 'G', + name: 'Floating Islands', + title: 'Floating Islands', + }, + { + id: '2572', + contest_id: 'JAGRegional2013', + problem_index: 'H', + name: 'Venn Diagram', + title: 'Venn Diagram', + }, + { + id: '2573', + contest_id: 'JAGRegional2013', + problem_index: 'I', + name: 'Overwriting Game', + title: 'Overwriting Game', + }, + { + id: '2574', + contest_id: 'JAGRegional2013', + problem_index: 'J', + name: 'Magical Switches', + title: 'Magical Switches', + }, + { + id: '2589', + contest_id: 'JAGRegional2014', + problem_index: 'A', + name: 'North North West', + title: 'North North West', + }, + { + id: '2590', + contest_id: 'JAGRegional2014', + problem_index: 'B', + name: 'Unknown Switches', + title: 'Unknown Switches', + }, + { + id: '2591', + contest_id: 'JAGRegional2014', + problem_index: 'C', + name: 'Speedrun', + title: 'Speedrun', + }, + { + id: '2592', + contest_id: 'JAGRegional2014', + problem_index: 'D', + name: 'Flowers', + title: 'Flowers', + }, + { + id: '2593', + contest_id: 'JAGRegional2014', + problem_index: 'E', + name: 'Square in Circles', + title: 'Square in Circles', + }, + { + id: '2594', + contest_id: 'JAGRegional2014', + problem_index: 'F', + name: 'Reverse a Road II', + title: 'Reverse a Road II', + grade: 'D3', + }, + { + id: '2595', + contest_id: 'JAGRegional2014', + problem_index: 'G', + name: 'Cookie Counter', + title: 'Cookie Counter', + }, + { + id: '2596', + contest_id: 'JAGRegional2014', + problem_index: 'H', + name: 'Points and Lines', + title: 'Points and Lines', + }, + { + id: '2597', + contest_id: 'JAGRegional2014', + problem_index: 'I', + name: 'Color the Map Extreme', + title: 'Color the Map Extreme', + }, + { + id: '2598', + contest_id: 'JAGRegional2014', + problem_index: 'J', + name: 'Website Tour', + title: 'Website Tour', + }, + { + id: '2599', + contest_id: 'JAGRegional2014', + problem_index: 'K', + name: 'Idempotent Filter', + title: 'Idempotent Filter', + }, + { + id: '2727', + contest_id: 'JAGRegional2015', + problem_index: 'A', + name: 'M and A', + title: 'M and A', + }, + { + id: '2728', + contest_id: 'JAGRegional2015', + problem_index: 'B', + name: 'Change a Password', + title: 'Change a Password', + }, + { + id: '2729', + contest_id: 'JAGRegional2015', + problem_index: 'C', + name: 'Delete Files', + title: 'Delete Files', + }, + { + id: '2730', + contest_id: 'JAGRegional2015', + problem_index: 'D', + name: 'Line Gimmick', + title: 'Line Gimmick', + }, + { + id: '2731', + contest_id: 'JAGRegional2015', + problem_index: 'E', + name: 'Shifting a Matrix', + title: 'Shifting a Matrix', + }, + { + id: '2732', + contest_id: 'JAGRegional2015', + problem_index: 'F', + name: 'Modern Announce Network', + title: 'Modern Announce Network', + }, + { + id: '2733', + contest_id: 'JAGRegional2015', + problem_index: 'G', + name: 'Cube Dividing', + title: 'Cube Dividing', + }, + { + id: '2734', + contest_id: 'JAGRegional2015', + problem_index: 'H', + name: 'Donut Decoration', + title: 'Donut Decoration', + }, + { + id: '2735', + contest_id: 'JAGRegional2015', + problem_index: 'I', + name: 'Shortest Bridge', + title: 'Shortest Bridge', + }, + { + id: '2736', + contest_id: 'JAGRegional2015', + problem_index: 'J', + name: 'Longest Shortest Path', + title: 'Longest Shortest Path', + grade: 'D5', + }, + { + id: '2737', + contest_id: 'JAGRegional2015', + problem_index: 'K', + name: 'Optimal Tournament', + title: 'Optimal Tournament', + }, + { + id: '2780', + contest_id: 'JAGRegional2016', + problem_index: 'A', + name: 'Best Matched Pair', + title: 'Best Matched Pair', + }, + { + id: '2781', + contest_id: 'JAGRegional2016', + problem_index: 'B', + name: 'Help the Princess!', + title: 'Help the Princess!', + }, + { + id: '2782', + contest_id: 'JAGRegional2016', + problem_index: 'C', + name: "We don't wanna work!", + title: "We don't wanna work!", + }, + { + id: '2783', + contest_id: 'JAGRegional2016', + problem_index: 'D', + name: 'Parentheses', + title: 'Parentheses', + }, + { + id: '2784', + contest_id: 'JAGRegional2016', + problem_index: 'E', + name: 'Similarity of Subtrees', + title: 'Similarity of Subtrees', + }, + { + id: '2785', + contest_id: 'JAGRegional2016', + problem_index: 'F', + name: 'Escape from the Hell', + title: 'Escape from the Hell', + }, + { + id: '2786', + contest_id: 'JAGRegional2016', + problem_index: 'G', + name: 'Share the Ruins Preservation', + title: 'Share the Ruins Preservation', + }, + { + id: '2787', + contest_id: 'JAGRegional2016', + problem_index: 'H', + name: 'Pipe Fitter and the Fierce Dogs', + title: 'Pipe Fitter and the Fierce Dogs', + }, + { + id: '2788', + contest_id: 'JAGRegional2016', + problem_index: 'I', + name: 'Multisect', + title: 'Multisect', + }, + { + id: '2789', + contest_id: 'JAGRegional2016', + problem_index: 'J', + name: 'Compressed Formula', + title: 'Compressed Formula', + }, + { + id: '2790', + contest_id: 'JAGRegional2016', + problem_index: 'K', + name: 'Non-redundant Drive', + title: 'Non-redundant Drive', + }, + { + id: '2856', + contest_id: 'JAGRegional2017', + problem_index: 'A', + name: 'Window', + title: 'Window', + }, + { + id: '2857', + contest_id: 'JAGRegional2017', + problem_index: 'B', + name: 'Tournament Chart', + title: 'Tournament Chart', + }, + { + id: '2858', + contest_id: 'JAGRegional2017', + problem_index: 'C', + name: 'Prime-Factor Prime', + title: 'Prime-Factor Prime', + grade: 'D1', + }, + { + id: '2859', + contest_id: 'JAGRegional2017', + problem_index: 'D', + name: 'Revenge of the Broken Door', + title: 'Revenge of the Broken Door', + }, + { + id: '2860', + contest_id: 'JAGRegional2017', + problem_index: 'E', + name: 'Tree Separator', + title: 'Tree Separator', + }, + { + id: '2861', + contest_id: 'JAGRegional2017', + problem_index: 'F', + name: 'RPG Maker', + title: 'RPG Maker', + }, + { + id: '2862', + contest_id: 'JAGRegional2017', + problem_index: 'G', + name: 'Coin Slider', + title: 'Coin Slider', + }, + { + id: '2863', + contest_id: 'JAGRegional2017', + problem_index: 'H', + name: 'Separate String', + title: 'Separate String', + grade: 'D2', + }, + { + id: '2864', + contest_id: 'JAGRegional2017', + problem_index: 'I', + name: 'Revenge of the Endless BFS', + title: 'Revenge of the Endless BFS', + }, + { + id: '2865', + contest_id: 'JAGRegional2017', + problem_index: 'J', + name: 'Farm Village', + title: 'Farm Village', + grade: 'D4', + }, + { + id: '2866', + contest_id: 'JAGRegional2017', + problem_index: 'K', + name: 'Conveyor Belt', + title: 'Conveyor Belt', + }, + { + id: '3218', + contest_id: 'JAGRegional2020', + problem_index: 'A', + name: 'On-Call', + title: 'On-Call', + }, + { + id: '3219', + contest_id: 'JAGRegional2020', + problem_index: 'B', + name: 'Warp Points', + title: 'Warp Points', + }, + { + id: '3220', + contest_id: 'JAGRegional2020', + problem_index: 'C', + name: 'Rebound Sequences', + title: 'Rebound Sequences', + }, + { + id: '3221', + contest_id: 'JAGRegional2020', + problem_index: 'D', + name: 'Surround the Castle', + title: 'Surround the Castle', + }, + { + id: '3222', + contest_id: 'JAGRegional2020', + problem_index: 'E', + name: 'Buttons', + title: 'Buttons', + }, + { + id: '3223', + contest_id: 'JAGRegional2020', + problem_index: 'F', + name: 'Mountain View', + title: 'Mountain View', + }, + { + id: '3224', + contest_id: 'JAGRegional2020', + problem_index: 'G', + name: 'Yet Another Expression Mining', + title: 'Yet Another Expression Mining', + }, + { + id: '3225', + contest_id: 'JAGRegional2020', + problem_index: 'H', + name: 'Aggressive Traveller', + title: 'Aggressive Traveller', + }, + { + id: '3226', + contest_id: 'JAGRegional2020', + problem_index: 'I', + name: 'Irreversible Reactions', + title: 'Irreversible Reactions', + }, + { + id: '3227', + contest_id: 'JAGRegional2020', + problem_index: 'J', + name: 'X-percent Blooming', + title: 'X-percent Blooming', + }, + { + id: '3228', + contest_id: 'JAGRegional2020', + problem_index: 'K', + name: 'Feed candies', + title: 'Feed candies', + }, + { + id: '3300', + contest_id: 'JAGRegional2021', + problem_index: 'A', + name: 'Harvest', + title: 'Harvest', + }, + { + id: '3301', + contest_id: 'JAGRegional2021', + problem_index: 'B', + name: 'Parse the Syntax Tree', + title: 'Parse the Syntax Tree', + }, + { + id: '3302', + contest_id: 'JAGRegional2021', + problem_index: 'C', + name: 'Permutation Magic', + title: 'Permutation Magic', + }, + { + id: '3303', + contest_id: 'JAGRegional2021', + problem_index: 'D', + name: 'MFP: Most Fluctuated Player', + title: 'MFP: Most Fluctuated Player', + }, + { + id: '3304', + contest_id: 'JAGRegional2021', + problem_index: 'E', + name: "Underground's SUNDAY", + title: "Underground's SUNDAY", + }, + { + id: '3305', + contest_id: 'JAGRegional2021', + problem_index: 'F', + name: 'qarentheziz zepuence', + title: 'qarentheziz zepuence', + }, + { + id: '3306', + contest_id: 'JAGRegional2021', + problem_index: 'G', + name: 'Pizza delivery', + title: 'Pizza delivery', + }, + { + id: '3307', + contest_id: 'JAGRegional2021', + problem_index: 'H', + name: 'include', + title: 'include', + }, + { + id: '3308', + contest_id: 'JAGRegional2021', + problem_index: 'I', + name: '(N+1)-legged race', + title: '(N+1)-legged race', + }, + { + id: '3309', + contest_id: 'JAGRegional2021', + problem_index: 'J', + name: 'Isomorphic?', + title: 'Isomorphic?', + }, + { + id: '3310', + contest_id: 'JAGRegional2021', + problem_index: 'K', + name: 'Zombie Land 2', + title: 'Zombie Land 2', + }, + { + id: '3346', + contest_id: 'JAGRegional2022', + problem_index: 'A', + name: 'Stop and Go', + title: 'Stop and Go', + }, + { + id: '3347', + contest_id: 'JAGRegional2022', + problem_index: 'B', + name: '1-Player Concentration', + title: '1-Player Concentration', + }, + { + id: '3348', + contest_id: 'JAGRegional2022', + problem_index: 'C', + name: 'Track Train Trail', + title: 'Track Train Trail', + }, + { + id: '3349', + contest_id: 'JAGRegional2022', + problem_index: 'D', + name: '2-LIS', + title: '2-LIS', + }, + { + id: '3350', + contest_id: 'JAGRegional2022', + problem_index: 'E', + name: 'Sharp 2-SAT', + title: 'Sharp 2-SAT', + }, + { + id: '3351', + contest_id: 'JAGRegional2022', + problem_index: 'F', + name: 'Seimei Handan 999.0', + title: 'Seimei Handan 999.0', + }, + { + id: '3352', + contest_id: 'JAGRegional2022', + problem_index: 'G', + name: 'Avoid bombings', + title: 'Avoid bombings', + }, + { + id: '3353', + contest_id: 'JAGRegional2022', + problem_index: 'H', + name: 'Sloppy city planning', + title: 'Sloppy city planning', + }, + { + id: '3354', + contest_id: 'JAGRegional2022', + problem_index: 'I', + name: 'N-1 Truths and a Lie', + title: 'N-1 Truths and a Lie', + }, + { + id: '3355', + contest_id: 'JAGRegional2022', + problem_index: 'J', + name: 'Most distant point from the stations', + title: 'Most distant point from the stations', + }, + { + id: '3356', + contest_id: 'JAGRegional2022', + problem_index: 'K', + name: 'Sort Compressed Strings', + title: 'Sort Compressed Strings', + }, + { + id: '3357', + contest_id: 'JAGRegional2022', + problem_index: 'L', + name: 'Tree Automorphism', + title: 'Tree Automorphism', + }, { id: '2389', contest_id: 'JAGSpring2012', - problem_index: '2389', + problem_index: 'A', name: 'Asynchronous Exceptions', - title: '2389. Asynchronous Exceptions', + title: 'Asynchronous Exceptions', }, { id: '2390', contest_id: 'JAGSpring2012', - problem_index: '2390', + problem_index: 'B', name: 'AYBABTU', - title: '2390. AYBABTU', + title: 'AYBABTU', }, { id: '2391', contest_id: 'JAGSpring2012', - problem_index: '2391', + problem_index: 'C', name: 'Billiards Sorting', - title: '2391. Billiards Sorting', + title: 'Billiards Sorting', }, { id: '2392', contest_id: 'JAGSpring2012', - problem_index: '2392', + problem_index: 'D', name: 'Digit', - title: '2392. Digit', + title: 'Digit', }, { id: '2393', contest_id: 'JAGSpring2012', - problem_index: '2393', + problem_index: 'E', name: 'Dungeon Creation', - title: '2393. Dungeon Creation', + title: 'Dungeon Creation', }, { id: '2394', contest_id: 'JAGSpring2012', - problem_index: '2394', + problem_index: 'F', name: 'Longest Lane', - title: '2394. Longest Lane', + title: 'Longest Lane', }, { id: '2395', contest_id: 'JAGSpring2012', - problem_index: '2395', + problem_index: 'G', name: 'Play in Basic', - title: '2395. Play in Basic', + title: 'Play in Basic', }, { id: '2396', contest_id: 'JAGSpring2012', - problem_index: '2396', + problem_index: 'H', name: 'Skyland', - title: '2396. Skyland', + title: 'Skyland', }, { id: '2397', contest_id: 'JAGSpring2012', - problem_index: '2397', + problem_index: 'I', name: 'Three-way Branch', - title: '2397. Three-way Branch', + title: 'Three-way Branch', }, { id: '2398', contest_id: 'JAGSpring2012', - problem_index: '2398', + problem_index: 'J', name: 'Tree Allocation', - title: '2398. Tree Allocation', + title: 'Tree Allocation', }, { id: '2555', contest_id: 'JAGSpring2013', - problem_index: '2555', + problem_index: 'A', name: 'Everlasting Zero', - title: '2555. Everlasting Zero', + title: 'Everlasting Zero', }, { id: '2556', contest_id: 'JAGSpring2013', - problem_index: '2556', + problem_index: 'B', name: 'Integer in Integer', - title: '2556. Integer in Integer', + title: 'Integer in Integer', }, { id: '2557', contest_id: 'JAGSpring2013', - problem_index: '2557', + problem_index: 'C', name: 'Iyasugigappa', - title: '2557. Iyasugigappa', + title: 'Iyasugigappa', }, { id: '2558', contest_id: 'JAGSpring2013', - problem_index: '2558', + problem_index: 'D', name: 'Medical Inspection', - title: '2558. Medical Inspection', + title: 'Medical Inspection', }, { id: '2559', contest_id: 'JAGSpring2013', - problem_index: '2559', + problem_index: 'E', name: 'Minimum Spanning Tree', - title: '2559. Minimum Spanning Tree', + title: 'Minimum Spanning Tree', }, { id: '2560', contest_id: 'JAGSpring2013', - problem_index: '2560', + problem_index: 'F', name: 'Point Distance', - title: '2560. Point Distance', + title: 'Point Distance', }, { id: '2561', contest_id: 'JAGSpring2013', - problem_index: '2561', + problem_index: 'G', name: 'Revenge of Minimum Cost Flow', - title: '2561. Revenge of Minimum Cost Flow', + title: 'Revenge of Minimum Cost Flow', }, { id: '2562', contest_id: 'JAGSpring2013', - problem_index: '2562', + problem_index: 'H', name: 'Rings', - title: '2562. Rings', + title: 'Rings', }, { id: '2563', contest_id: 'JAGSpring2013', - problem_index: '2563', + problem_index: 'I', name: 'The J-th Number', - title: '2563. The J-th Number', + title: 'The J-th Number', }, { id: '2564', contest_id: 'JAGSpring2013', - problem_index: '2564', + problem_index: 'J', name: 'Tree Reconstruction', - title: '2564. Tree Reconstruction', + title: 'Tree Reconstruction', }, { id: '2677', contest_id: 'JAGSpring2014', - problem_index: '2677', + problem_index: 'A', name: 'Breadth-First Search by Foxpower', - title: '2677. Breadth-First Search by Foxpower', + title: 'Breadth-First Search by Foxpower', }, { id: '2678', contest_id: 'JAGSpring2014', - problem_index: '2678', + problem_index: 'B', name: 'Cube Coloring', - title: '2678. Cube Coloring', + title: 'Cube Coloring', }, { id: '2679', contest_id: 'JAGSpring2014', - problem_index: '2679', + problem_index: 'C', name: 'Decoding Ancient Messages', - title: '2679. Decoding Ancient Messages', + title: 'Decoding Ancient Messages', }, { id: '2680', contest_id: 'JAGSpring2014', - problem_index: '2680', + problem_index: 'D', name: 'LR', - title: '2680. LR', + title: 'LR', }, { id: '2681', contest_id: 'JAGSpring2014', - problem_index: '2681', + problem_index: 'E', name: 'Parentheses', - title: '2681. Parentheses', + title: 'Parentheses', }, { id: '2682', contest_id: 'JAGSpring2014', - problem_index: '2682', + problem_index: 'F', name: 'Polygon Guards', - title: '2682. Polygon Guards', + title: 'Polygon Guards', }, { id: '2683', contest_id: 'JAGSpring2014', - problem_index: '2683', + problem_index: 'G', name: 'Proportional Representation', - title: '2683. Proportional Representation', + title: 'Proportional Representation', }, { id: '2684', contest_id: 'JAGSpring2014', - problem_index: '2684', + problem_index: 'H', name: 'RLE Replacement', - title: '2684. RLE Replacement', + title: 'RLE Replacement', }, { id: '2685', contest_id: 'JAGSpring2014', - problem_index: '2685', + problem_index: 'I', name: 'Tokyo Olympics Center', - title: '2685. Tokyo Olympics Center', + title: 'Tokyo Olympics Center', }, { id: '2686', contest_id: 'JAGSpring2014', - problem_index: '2686', + problem_index: 'J', name: 'Unfair Game', - title: '2686. Unfair Game', + title: 'Unfair Game', }, { id: '2687', contest_id: 'JAGSpring2015', - problem_index: '2687', + problem_index: 'A', name: 'Balanced Paths', - title: '2687. Balanced Paths', + title: 'Balanced Paths', }, { id: '2688', contest_id: 'JAGSpring2015', - problem_index: '2688', + problem_index: 'B', name: 'Card Game Strategy', - title: '2688. Card Game Strategy', + title: 'Card Game Strategy', }, { id: '2689', contest_id: 'JAGSpring2015', - problem_index: '2689', + problem_index: 'C', name: 'Casino', - title: '2689. Casino', + title: 'Casino', }, { id: '2690', contest_id: 'JAGSpring2015', - problem_index: '2690', + problem_index: 'D', name: 'Content Delivery', - title: '2690. Content Delivery', + title: 'Content Delivery', }, { id: '2691', contest_id: 'JAGSpring2015', - problem_index: '2691', + problem_index: 'E', name: 'Cost Performance Flow', - title: '2691. Cost Performance Flow', + title: 'Cost Performance Flow', }, { id: '2692', contest_id: 'JAGSpring2015', - problem_index: '2692', + problem_index: 'F', name: 'ICPC Teams', - title: '2692. ICPC Teams', + title: 'ICPC Teams', }, { id: '2693', contest_id: 'JAGSpring2015', - problem_index: '2693', + problem_index: 'G', name: 'JAG-channel II', - title: '2693. JAG-channel II', + title: 'JAG-channel II', }, { id: '2694', contest_id: 'JAGSpring2015', - problem_index: '2694', + problem_index: 'H', name: 'Kimagure Cleaner', - title: '2694. Kimagure Cleaner', + title: 'Kimagure Cleaner', }, { id: '2695', contest_id: 'JAGSpring2015', - problem_index: '2695', + problem_index: 'I', name: 'Midpoint', - title: '2695. Midpoint', + title: 'Midpoint', }, { id: '2696', contest_id: 'JAGSpring2015', - problem_index: '2696', + problem_index: 'J', name: 'New Game AI', - title: '2696. New Game AI', + title: 'New Game AI', }, { id: '2697', contest_id: 'JAGSpring2015', - problem_index: '2697', + problem_index: 'K', name: 'Runner and Sniper', - title: '2697. Runner and Sniper', + title: 'Runner and Sniper', }, { id: '2698', contest_id: 'JAGSpring2015', - problem_index: '2698', + problem_index: 'L', name: 'Wall Making Game', - title: '2698. Wall Making Game', + title: 'Wall Making Game', }, { id: '2057', contest_id: 'JAGSummer2006-day1', - problem_index: '2057', + problem_index: 'B', name: 'The Closest Circle', - title: '2057. The Closest Circle', + title: 'The Closest Circle', }, { id: '2058', contest_id: 'JAGSummer2006-day2', - problem_index: '2058', + problem_index: 'A', name: 'Moduic Squares', - title: '2058. Moduic Squares', + title: 'Moduic Squares', }, { id: '2059', contest_id: 'JAGSummer2006-day2', - problem_index: '2059', + problem_index: 'B', name: 'Restaurant', - title: '2059. Restaurant', + title: 'Restaurant', }, { id: '2060', contest_id: 'JAGSummer2006-day2', - problem_index: '2060', + problem_index: 'C', name: 'Tetrahedra', - title: '2060. Tetrahedra', + title: 'Tetrahedra', }, { id: '2061', contest_id: 'JAGSummer2006-day2', - problem_index: '2061', + problem_index: 'D', name: 'International Party', - title: '2061. International Party', + title: 'International Party', }, { id: '2062', contest_id: 'JAGSummer2006-day2', - problem_index: '2062', + problem_index: 'E', name: 'Hide-and-seek', - title: '2062. Hide-and-seek', + title: 'Hide-and-seek', }, { id: '2063', contest_id: 'JAGSummer2006-day2', - problem_index: '2063', + problem_index: 'F', name: 'TV Watching', - title: '2063. TV Watching', + title: 'TV Watching', }, { id: '2064', contest_id: 'JAGSummer2006-day2', - problem_index: '2064', + problem_index: 'G', name: 'Make Friendships', - title: '2064. Make Friendships', + title: 'Make Friendships', }, { id: '2065', contest_id: 'JAGSummer2006-day2', - problem_index: '2065', + problem_index: 'H', name: 'Slippy Floors', - title: '2065. Slippy Floors', + title: 'Slippy Floors', }, { id: '2066', contest_id: 'JAGSummer2006-day2', - problem_index: '2066', + problem_index: 'I', name: 'Roads in a City', - title: '2066. Roads in a City', + title: 'Roads in a City', }, { id: '2067', contest_id: 'JAGSummer2006-day3', - problem_index: '2067', + problem_index: 'B', name: 'Reading Brackets in English', - title: '2067. Reading Brackets in English', + title: 'Reading Brackets in English', }, { id: '2068', contest_id: 'JAGSummer2006-day3', - problem_index: '2068', + problem_index: 'C', name: 'Grated Radish', - title: '2068. Grated Radish', + title: 'Grated Radish', }, { id: '2069', contest_id: 'JAGSummer2006-day3', - problem_index: '2069', + problem_index: 'D', name: 'Greedy, Greedy.', - title: '2069. Greedy, Greedy.', + title: 'Greedy, Greedy.', }, { id: '2070', contest_id: 'JAGSummer2006-day3', - problem_index: '2070', + problem_index: 'E', name: 'First Experience', - title: '2070. First Experience', + title: 'First Experience', }, { id: '2071', contest_id: 'JAGSummer2006-day3', - problem_index: '2071', + problem_index: 'F', name: 'Web 0.5', - title: '2071. Web 0.5', + title: 'Web 0.5', }, { id: '2072', contest_id: 'JAGSummer2006-day3', - problem_index: '2072', + problem_index: 'G', name: "Philosopher's Stone", - title: "2072. Philosopher's Stone", + title: "Philosopher's Stone", }, { id: '2079', contest_id: 'JAGSummer2007-day1', - problem_index: '2079', + problem_index: 'A', name: 'Dance Dance Revolution', - title: '2079. Dance Dance Revolution', + title: 'Dance Dance Revolution', }, { id: '2080', contest_id: 'JAGSummer2007-day1', - problem_index: '2080', + problem_index: 'B', name: 'Compress Files', - title: '2080. Compress Files', + title: 'Compress Files', }, { id: '2081', contest_id: 'JAGSummer2007-day1', - problem_index: '2081', + problem_index: 'C', name: 'Save the Energy', - title: '2081. Save the Energy', + title: 'Save the Energy', }, { id: '2082', contest_id: 'JAGSummer2007-day1', - problem_index: '2082', + problem_index: 'D', name: 'Goofy Converter', - title: '2082. Goofy Converter', + title: 'Goofy Converter', }, { id: '2083', contest_id: 'JAGSummer2007-day1', - problem_index: '2083', + problem_index: 'E', name: 'Black Force', - title: '2083. Black Force', + title: 'Black Force', }, { id: '2084', contest_id: 'JAGSummer2007-day2', - problem_index: '2084', + problem_index: 'A', name: 'Hit and Blow', - title: '2084. Hit and Blow', + title: 'Hit and Blow', }, { id: '2085', contest_id: 'JAGSummer2007-day2', - problem_index: '2085', + problem_index: 'B', name: 'Turn Left', - title: '2085. Turn Left', + title: 'Turn Left', }, { id: '2086', contest_id: 'JAGSummer2007-day2', - problem_index: '2086', + problem_index: 'C', name: '!', - title: '2086. !', + title: '!', }, { id: '2087', contest_id: 'JAGSummer2007-day2', - problem_index: '2087', + problem_index: 'D', name: 'Speed', - title: '2087. Speed', + title: 'Speed', }, { id: '2088', contest_id: 'JAGSummer2007-day2', - problem_index: '2088', + problem_index: 'E', name: 'Spirograph', - title: '2088. Spirograph', + title: 'Spirograph', }, { id: '2089', contest_id: 'JAGSummer2007-day2', - problem_index: '2089', + problem_index: 'F', name: 'Mysterious Dungeons', - title: '2089. Mysterious Dungeons', + title: 'Mysterious Dungeons', }, { id: '2090', contest_id: 'JAGSummer2007-day2', - problem_index: '2090', + problem_index: 'G', name: 'Repeated Subsequences', - title: '2090. Repeated Subsequences', + title: 'Repeated Subsequences', }, { id: '2091', contest_id: 'JAGSummer2007-day2', - problem_index: '2091', + problem_index: 'H', name: 'Petoris', - title: '2091. Petoris', + title: 'Petoris', }, { id: '2092', contest_id: 'JAGSummer2007-day2', - problem_index: '2092', + problem_index: 'I', name: 'Pythagoraslope', - title: '2092. Pythagoraslope', + title: 'Pythagoraslope', }, { id: '2093', contest_id: 'JAGSummer2007-day3', - problem_index: '2093', + problem_index: 'A', name: 'Election', - title: '2093. Election', + title: 'Election', }, { id: '2094', contest_id: 'JAGSummer2007-day3', - problem_index: '2094', + problem_index: 'B', name: 'Jaggie Spheres', - title: '2094. Jaggie Spheres', + title: 'Jaggie Spheres', }, { id: '2095', contest_id: 'JAGSummer2007-day3', - problem_index: '2095', + problem_index: 'C', name: 'Nagashi Soumen', - title: '2095. Nagashi Soumen', + title: 'Nagashi Soumen', }, { id: '2096', contest_id: 'JAGSummer2007-day3', - problem_index: '2096', + problem_index: 'D', name: 'Private Teacher', - title: '2096. Private Teacher', + title: 'Private Teacher', }, { id: '2097', contest_id: 'JAGSummer2007-day3', - problem_index: '2097', + problem_index: 'E', name: 'Triangles', - title: '2097. Triangles', + title: 'Triangles', }, { id: '2098', contest_id: 'JAGSummer2007-day3', - problem_index: '2098', + problem_index: 'F', name: 'Two-finger Programming', - title: '2098. Two-finger Programming', + title: 'Two-finger Programming', }, { id: '2099', contest_id: 'JAGSummer2007-day3', - problem_index: '2099', + problem_index: 'G', name: 'Walk under a Scorching Sun', - title: '2099. Walk under a Scorching Sun', + title: 'Walk under a Scorching Sun', }, { id: '2131', contest_id: 'JAGSummer2008-day2', - problem_index: '2131', + problem_index: 'A', name: 'Pi is Three', - title: '2131. Pi is Three', + title: 'Pi is Three', }, { id: '2132', contest_id: 'JAGSummer2008-day2', - problem_index: '2132', + problem_index: 'B', name: 'Left Hand Rule', - title: '2132. Left Hand Rule', + title: 'Left Hand Rule', }, { id: '2133', contest_id: 'JAGSummer2008-day2', - problem_index: '2133', + problem_index: 'C', name: 'Alice and Bob', - title: '2133. Alice and Bob', + title: 'Alice and Bob', }, { id: '2134', contest_id: 'JAGSummer2008-day2', - problem_index: '2134', + problem_index: 'D', name: 'Deadly Dice Game', - title: '2134. Deadly Dice Game', + title: 'Deadly Dice Game', }, { id: '2135', contest_id: 'JAGSummer2008-day2', - problem_index: '2135', + problem_index: 'E', name: 'Reverse a Road', - title: '2135. Reverse a Road', + title: 'Reverse a Road', }, { id: '2136', contest_id: 'JAGSummer2008-day2', - problem_index: '2136', + problem_index: 'F', name: 'Webby Subway', - title: '2136. Webby Subway', + title: 'Webby Subway', }, { id: '2137', contest_id: 'JAGSummer2008-day2', - problem_index: '2137', + problem_index: 'G', name: 'Time Trial', - title: '2137. Time Trial', + title: 'Time Trial', }, { id: '2138', contest_id: 'JAGSummer2008-day2', - problem_index: '2138', + problem_index: 'H', name: 'Vending Machine', - title: '2138. Vending Machine', + title: 'Vending Machine', }, { id: '2139', contest_id: 'JAGSummer2008-day2', - problem_index: '2139', + problem_index: 'I', name: 'Memory Match', - title: '2139. Memory Match', + title: 'Memory Match', }, { id: '2140', contest_id: 'JAGSummer2008-day2', - problem_index: '2140', + problem_index: 'J', name: 'Tile Puzzle', - title: '2140. Tile Puzzle', + title: 'Tile Puzzle', }, { id: '2141', contest_id: 'JAGSummer2008-day4', - problem_index: '2141', + problem_index: 'A', name: "Girls' Party", - title: "2141. Girls' Party", + title: "Girls' Party", }, { id: '2142', contest_id: 'JAGSummer2008-day4', - problem_index: '2142', + problem_index: 'B', name: 'Bitwise Kingdom', - title: '2142. Bitwise Kingdom', + title: 'Bitwise Kingdom', }, { id: '2143', contest_id: 'JAGSummer2008-day4', - problem_index: '2143', + problem_index: 'C', name: 'Adaptive Time Slicing Quantization', - title: '2143. Adaptive Time Slicing Quantization', + title: 'Adaptive Time Slicing Quantization', }, { id: '2144', contest_id: 'JAGSummer2008-day4', - problem_index: '2144', + problem_index: 'D', name: 'Reaction', - title: '2144. Reaction', + title: 'Reaction', }, { id: '2145', contest_id: 'JAGSummer2008-day4', - problem_index: '2145', + problem_index: 'E', name: 'Magical Island', - title: '2145. Magical Island', + title: 'Magical Island', }, { id: '2146', contest_id: 'JAGSummer2008-day4', - problem_index: '2146', + problem_index: 'F', name: 'Ninja Legend', - title: '2146. Ninja Legend', + title: 'Ninja Legend', }, { id: '2147', contest_id: 'JAGSummer2008-day4', - problem_index: '2147', + problem_index: 'G', name: 'Do It', - title: '2147. Do It', + title: 'Do It', }, { id: '2148', contest_id: 'JAGSummer2008-day4', - problem_index: '2148', + problem_index: 'H', name: 'Robot Communication', - title: '2148. Robot Communication', + title: 'Robot Communication', }, { id: '2155', contest_id: 'JAGSummer2009-day2', - problem_index: '2155', + problem_index: 'A', name: 'Infected Computer', - title: '2155. Infected Computer', + title: 'Infected Computer', }, { id: '2156', contest_id: 'JAGSummer2009-day2', - problem_index: '2156', + problem_index: 'B', name: 'Magic Slayer', - title: '2156. Magic Slayer', + title: 'Magic Slayer', }, { id: '2157', contest_id: 'JAGSummer2009-day2', - problem_index: '2157', + problem_index: 'C', name: 'Dial Lock', - title: '2157. Dial Lock', + title: 'Dial Lock', }, { id: '2158', contest_id: 'JAGSummer2009-day2', - problem_index: '2158', + problem_index: 'D', name: 'Double Sorting', - title: '2158. Double Sorting', + title: 'Double Sorting', }, { id: '2159', contest_id: 'JAGSummer2009-day2', - problem_index: '2159', + problem_index: 'E', name: 'Symmetry', - title: '2159. Symmetry', + title: 'Symmetry', }, { id: '2160', contest_id: 'JAGSummer2009-day2', - problem_index: '2160', + problem_index: 'F', name: 'Voronoi Island', - title: '2160. Voronoi Island', + title: 'Voronoi Island', }, { id: '2161', contest_id: 'JAGSummer2009-day2', - problem_index: '2161', + problem_index: 'G', name: 'Defend the Bases', - title: '2161. Defend the Bases', + title: 'Defend the Bases', }, { id: '2162', contest_id: 'JAGSummer2009-day2', - problem_index: '2162', + problem_index: 'H', name: 'Galaxy Wide Web Service', - title: '2162. Galaxy Wide Web Service', + title: 'Galaxy Wide Web Service', }, { id: '2163', contest_id: 'JAGSummer2009-day2', - problem_index: '2163', + problem_index: 'I', name: 'Tatami', - title: '2163. Tatami', + title: 'Tatami', }, { id: '2164', contest_id: 'JAGSummer2009-day2', - problem_index: '2164', + problem_index: 'J', name: 'Revenge of the Round Table', - title: '2164. Revenge of the Round Table', + title: 'Revenge of the Round Table', }, { id: '2165', contest_id: 'JAGSummer2009-day3', - problem_index: '2165', + problem_index: 'A', name: 'Strange String Manipulation', - title: '2165. Strange String Manipulation', + title: 'Strange String Manipulation', }, { id: '2166', contest_id: 'JAGSummer2009-day3', - problem_index: '2166', + problem_index: 'B', name: 'Erratic Sleep Habits', - title: '2166. Erratic Sleep Habits', + title: 'Erratic Sleep Habits', }, { id: '2167', contest_id: 'JAGSummer2009-day3', - problem_index: '2167', + problem_index: 'C', name: 'Find the Point', - title: '2167. Find the Point', + title: 'Find the Point', }, { id: '2168', contest_id: 'JAGSummer2009-day3', - problem_index: '2168', + problem_index: 'D', name: "Luigi's Tavern", - title: "2168. Luigi's Tavern", + title: "Luigi's Tavern", }, { id: '2169', contest_id: 'JAGSummer2009-day3', - problem_index: '2169', + problem_index: 'E', name: 'Colored Octahedra', - title: '2169. Colored Octahedra', + title: 'Colored Octahedra', }, { id: '2170', contest_id: 'JAGSummer2009-day3', - problem_index: '2170', + problem_index: 'F', name: 'Marked Ancestor', - title: '2170. Marked Ancestor', + title: 'Marked Ancestor', }, { id: '2171', contest_id: 'JAGSummer2009-day3', - problem_index: '2171', + problem_index: 'G', name: 'Strange Couple', - title: '2171. Strange Couple', + title: 'Strange Couple', }, { id: '2172', contest_id: 'JAGSummer2009-day3', - problem_index: '2172', + problem_index: 'H', name: "Queen's Case", - title: "2172. Queen's Case", + title: "Queen's Case", }, { id: '2173', contest_id: 'JAGSummer2009-day3', - problem_index: '2173', + problem_index: 'I', name: 'Wind Passages', - title: '2173. Wind Passages', + title: 'Wind Passages', }, { id: '2174', contest_id: 'JAGSummer2009-day3', - problem_index: '2174', + problem_index: 'J', name: 'Secret Operation', - title: '2174. Secret Operation', + title: 'Secret Operation', }, { id: '2330', contest_id: 'JAGSummer2010-day2', - problem_index: '2330', + problem_index: 'A', name: 'Earth Invasion Diary of Miyabi-sensei', - title: '2330. Earth Invasion Diary of Miyabi-sensei', + title: 'Earth Invasion Diary of Miyabi-sensei', }, { id: '2331', contest_id: 'JAGSummer2010-day2', - problem_index: '2331', + problem_index: 'B', name: 'A Way to Invite Friends', - title: '2331. A Way to Invite Friends', + title: 'A Way to Invite Friends', }, { id: '2332', contest_id: 'JAGSummer2010-day2', - problem_index: '2332', + problem_index: 'C', name: 'Space-Time Sugoroku Road', - title: '2332. Space-Time Sugoroku Road', + title: 'Space-Time Sugoroku Road', }, { id: '2333', contest_id: 'JAGSummer2010-day2', - problem_index: '2333', + problem_index: 'D', name: 'My friends are small', - title: '2333. My friends are small', + title: 'My friends are small', }, { id: '2334', contest_id: 'JAGSummer2010-day2', - problem_index: '2334', + problem_index: 'E', name: 'Roads on Towns', - title: '2334. Roads on Towns', + title: 'Roads on Towns', }, { id: '2335', contest_id: 'JAGSummer2010-day2', - problem_index: '2335', + problem_index: 'F', name: '10-Year-Old Dynamic Programming', - title: '2335. 10-Year-Old Dynamic Programming', + title: '10-Year-Old Dynamic Programming', }, { id: '2336', contest_id: 'JAGSummer2010-day2', - problem_index: '2336', + problem_index: 'G', name: 'Spring Tiles', - title: '2336. Spring Tiles', + title: 'Spring Tiles', }, { id: '2337', contest_id: 'JAGSummer2010-day2', - problem_index: '2337', + problem_index: 'H', name: 'Remodeling Plan for Neko-Nabe (tentative)', - title: '2337. Remodeling Plan for Neko-Nabe (tentative)', + title: 'Remodeling Plan for Neko-Nabe (tentative)', }, { id: '2338', contest_id: 'JAGSummer2010-day2', - problem_index: '2338', + problem_index: 'I', name: 'Intelligible Double Magic', - title: '2338. Intelligible Double Magic', + title: 'Intelligible Double Magic', }, { id: '2339', contest_id: 'JAGSummer2010-day2', - problem_index: '2339', + problem_index: 'J', name: "Person responsible for problem description don't w", - title: "2339. Person responsible for problem description don't w", + title: "Person responsible for problem description don't w", }, { id: '2232', contest_id: 'JAGSummer2010-day3', - problem_index: '2232', + problem_index: 'A', name: 'Ennichi', - title: '2232. Ennichi', + title: 'Ennichi', }, { id: '2233', contest_id: 'JAGSummer2010-day3', - problem_index: '2233', + problem_index: 'B', name: 'Carrot Tour', - title: '2233. Carrot Tour', + title: 'Carrot Tour', }, { id: '2234', contest_id: 'JAGSummer2010-day3', - problem_index: '2234', + problem_index: 'C', name: 'Usagitobi', - title: '2234. Usagitobi', + title: 'Usagitobi', }, { id: '2235', contest_id: 'JAGSummer2010-day3', - problem_index: '2235', + problem_index: 'D', name: 'Graph Construction', - title: '2235. Graph Construction', + title: 'Graph Construction', }, { id: '2236', contest_id: 'JAGSummer2010-day3', - problem_index: '2236', + problem_index: 'E', name: 'Rabbit Plays Games!', - title: '2236. Rabbit Plays Games!', + title: 'Rabbit Plays Games!', }, { id: '2237', contest_id: 'JAGSummer2010-day3', - problem_index: '2237', + problem_index: 'F', name: 'The Castle', - title: '2237. The Castle', + title: 'The Castle', }, { id: '2238', contest_id: 'JAGSummer2010-day3', - problem_index: '2238', + problem_index: 'G', name: 'Nurie', - title: '2238. Nurie', + title: 'Nurie', }, { id: '2239', contest_id: 'JAGSummer2010-day3', - problem_index: '2239', + problem_index: 'H', name: 'Nearest Station', - title: '2239. Nearest Station', + title: 'Nearest Station', }, { id: '2240', contest_id: 'JAGSummer2010-day3', - problem_index: '2240', + problem_index: 'I', name: 'Lapin Noir', - title: '2240. Lapin Noir', + title: 'Lapin Noir', }, { id: '2241', contest_id: 'JAGSummer2010-day3', - problem_index: '2241', + problem_index: 'J', name: 'Usaneko Matrix', - title: '2241. Usaneko Matrix', + title: 'Usaneko Matrix', }, { id: '2222', contest_id: 'JAGSummer2010-day4', - problem_index: '2222', + problem_index: 'A', name: "Alien's Counting", - title: "2222. Alien's Counting", + title: "Alien's Counting", }, { id: '2223', contest_id: 'JAGSummer2010-day4', - problem_index: '2223', + problem_index: 'B', name: 'Kaeru Jump', - title: '2223. Kaeru Jump', + title: 'Kaeru Jump', }, { id: '2224', contest_id: 'JAGSummer2010-day4', - problem_index: '2224', + problem_index: 'C', name: 'Save your cats', - title: '2224. Save your cats', + title: 'Save your cats', }, { id: '2225', contest_id: 'JAGSummer2010-day4', - problem_index: '2225', + problem_index: 'D', name: 'Dungeon Wall', - title: '2225. Dungeon Wall', + title: 'Dungeon Wall', }, { id: '2226', contest_id: 'JAGSummer2010-day4', - problem_index: '2226', + problem_index: 'E', name: 'Psychic Accelerator', - title: '2226. Psychic Accelerator', + title: 'Psychic Accelerator', }, { id: '2227', contest_id: 'JAGSummer2010-day4', - problem_index: '2227', + problem_index: 'F', name: 'Bouldering', - title: '2227. Bouldering', + title: 'Bouldering', }, { id: '2228', contest_id: 'JAGSummer2010-day4', - problem_index: '2228', + problem_index: 'G', name: "DON'T PANIC!", - title: "2228. DON'T PANIC!", + title: "DON'T PANIC!", }, { id: '2229', contest_id: 'JAGSummer2010-day4', - problem_index: '2229', + problem_index: 'H', name: 'Ropeway', - title: '2229. Ropeway', + title: 'Ropeway', }, { id: '2230', contest_id: 'JAGSummer2010-day4', - problem_index: '2230', + problem_index: 'I', name: 'How to Create a Good Game', - title: '2230. How to Create a Good Game', + title: 'How to Create a Good Game', }, { id: '2231', contest_id: 'JAGSummer2010-day4', - problem_index: '2231', + problem_index: 'J', name: 'Cruel Bingo', - title: '2231. Cruel Bingo', + title: 'Cruel Bingo', }, { id: '2290', contest_id: 'JAGSummer2011-day2', - problem_index: '2290', + problem_index: 'A', name: 'Attack the Moles', - title: '2290. Attack the Moles', + title: 'Attack the Moles', }, { id: '2291', contest_id: 'JAGSummer2011-day2', - problem_index: '2291', + problem_index: 'B', name: 'Brilliant Stars', - title: '2291. Brilliant Stars', + title: 'Brilliant Stars', }, { id: '2292', contest_id: 'JAGSummer2011-day2', - problem_index: '2292', + problem_index: 'C', name: 'Common Palindromes', - title: '2292. Common Palindromes', + title: 'Common Palindromes', }, { id: '2293', contest_id: 'JAGSummer2011-day2', - problem_index: '2293', + problem_index: 'D', name: 'Dangerous Tower', - title: '2293. Dangerous Tower', + title: 'Dangerous Tower', }, { id: '2294', contest_id: 'JAGSummer2011-day2', - problem_index: '2294', + problem_index: 'E', name: 'Entangled with Lottery', - title: '2294. Entangled with Lottery', + title: 'Entangled with Lottery', }, { id: '2295', contest_id: 'JAGSummer2011-day2', - problem_index: '2295', + problem_index: 'F', name: 'Power of Power', - title: '2295. Power of Power', + title: 'Power of Power', }, { id: '2296', contest_id: 'JAGSummer2011-day2', - problem_index: '2296', + problem_index: 'G', name: 'Quest of Merchant', - title: '2296. Quest of Merchant', + title: 'Quest of Merchant', }, { id: '2297', contest_id: 'JAGSummer2011-day2', - problem_index: '2297', + problem_index: 'H', name: 'Rectangular Stamps', - title: '2297. Rectangular Stamps', + title: 'Rectangular Stamps', }, { id: '2298', contest_id: 'JAGSummer2011-day2', - problem_index: '2298', + problem_index: 'I', name: 'Starting Line', - title: '2298. Starting Line', + title: 'Starting Line', }, { id: '2299', contest_id: 'JAGSummer2011-day2', - problem_index: '2299', + problem_index: 'J', name: 'Tiles are Colorful', - title: '2299. Tiles are Colorful', + title: 'Tiles are Colorful', }, { id: '2300', contest_id: 'JAGSummer2011-day3', - problem_index: '2300', + problem_index: 'A', name: 'Calender Colors', - title: '2300. Calender Colors', + title: 'Calender Colors', }, { id: '2301', contest_id: 'JAGSummer2011-day3', - problem_index: '2301', + problem_index: 'B', name: 'Sleeping Time', - title: '2301. Sleeping Time', + title: 'Sleeping Time', }, { id: '2302', contest_id: 'JAGSummer2011-day3', - problem_index: '2302', + problem_index: 'C', name: 'On or Off', - title: '2302. On or Off', + title: 'On or Off', }, { id: '2303', contest_id: 'JAGSummer2011-day3', - problem_index: '2303', + problem_index: 'D', name: 'Marathon Match', - title: '2303. Marathon Match', + title: 'Marathon Match', }, { id: '2304', contest_id: 'JAGSummer2011-day3', - problem_index: '2304', + problem_index: 'E', name: 'Reverse Roads', - title: '2304. Reverse Roads', + title: 'Reverse Roads', }, { id: '2305', contest_id: 'JAGSummer2011-day3', - problem_index: '2305', + problem_index: 'F', name: 'Beautiful Currency', - title: '2305. Beautiful Currency', + title: 'Beautiful Currency', }, { id: '2306', contest_id: 'JAGSummer2011-day3', - problem_index: '2306', + problem_index: 'G', name: 'Rabbit Party', - title: '2306. Rabbit Party', + title: 'Rabbit Party', }, { id: '2307', contest_id: 'JAGSummer2011-day3', - problem_index: '2307', + problem_index: 'H', name: 'Palindrome Generator', - title: '2307. Palindrome Generator', + title: 'Palindrome Generator', }, { id: '2308', contest_id: 'JAGSummer2011-day3', - problem_index: '2308', + problem_index: 'I', name: 'White Bird', - title: '2308. White Bird', + title: 'White Bird', }, { id: '2309', contest_id: 'JAGSummer2011-day3', - problem_index: '2309', + problem_index: 'J', name: 'Vector Compression', - title: '2309. Vector Compression', + title: 'Vector Compression', }, { id: '2310', contest_id: 'JAGSummer2011-day4', - problem_index: '2310', + problem_index: 'A', name: 'Rose Garden Witch', - title: '2310. Rose Garden Witch', + title: 'Rose Garden Witch', }, { id: '2311', contest_id: 'JAGSummer2011-day4', - problem_index: '2311', + problem_index: 'B', name: 'Dessert Witch', - title: '2311. Dessert Witch', + title: 'Dessert Witch', }, { id: '2312', contest_id: 'JAGSummer2011-day4', - problem_index: '2312', + problem_index: 'C', name: 'Magical Girl Sayaka-chan', - title: '2312. Magical Girl Sayaka-chan', + title: 'Magical Girl Sayaka-chan', }, { id: '2313', contest_id: 'JAGSummer2011-day4', - problem_index: '2313', + problem_index: 'D', name: 'Box Witch', - title: '2313. Box Witch', + title: 'Box Witch', }, { id: '2314', contest_id: 'JAGSummer2011-day4', - problem_index: '2314', + problem_index: 'E', name: 'Scribbling witch', - title: '2314. Scribbling witch', + title: 'Scribbling witch', }, { id: '2315', contest_id: 'JAGSummer2011-day4', - problem_index: '2315', + problem_index: 'F', name: 'Shadow Witch', - title: '2315. Shadow Witch', + title: 'Shadow Witch', }, { id: '2316', contest_id: 'JAGSummer2011-day4', - problem_index: '2316', + problem_index: 'G', name: 'Mermaid Witch', - title: '2316. Mermaid Witch', + title: 'Mermaid Witch', }, { id: '2317', contest_id: 'JAGSummer2011-day4', - problem_index: '2317', + problem_index: 'H', name: 'Class Representative Witch', - title: '2317. Class Representative Witch', + title: 'Class Representative Witch', }, { id: '2318', contest_id: 'JAGSummer2011-day4', - problem_index: '2318', + problem_index: 'I', name: 'Set-constructing Witch', - title: '2318. Set-constructing Witch', + title: 'Set-constructing Witch', }, { id: '2319', contest_id: 'JAGSummer2011-day4', - problem_index: '2319', + problem_index: 'J', name: 'Soul Gem Game', - title: '2319. Soul Gem Game', + title: 'Soul Gem Game', }, { id: '2424', contest_id: 'JAGSummer2012-day2', - problem_index: '2424', + problem_index: 'A', name: 'Kakezan', - title: '2424. Kakezan', + title: 'Kakezan', }, { id: '2425', contest_id: 'JAGSummer2012-day2', - problem_index: '2425', + problem_index: 'B', name: 'A Holiday of Miss Brute Force', - title: '2425. A Holiday of Miss Brute Force', + title: 'A Holiday of Miss Brute Force', }, { id: '2426', contest_id: 'JAGSummer2012-day2', - problem_index: '2426', + problem_index: 'C', name: 'Treasure Hunt', - title: '2426. Treasure Hunt', + title: 'Treasure Hunt', }, { id: '2427', contest_id: 'JAGSummer2012-day2', - problem_index: '2427', + problem_index: 'D', name: 'hosonagaitokoro', - title: '2427. hosonagaitokoro', + title: 'hosonagaitokoro', }, { id: '2428', contest_id: 'JAGSummer2012-day2', - problem_index: '2428', + problem_index: 'E', name: 'Lost Number', - title: '2428. Lost Number', + title: 'Lost Number', }, { id: '2429', contest_id: 'JAGSummer2012-day2', - problem_index: '2429', + problem_index: 'F', name: 'marukaite', - title: '2429. marukaite', + title: 'marukaite', }, { id: '2430', contest_id: 'JAGSummer2012-day2', - problem_index: '2430', + problem_index: 'G', name: 'Longest Increasing Sequence', - title: '2430. Longest Increasing Sequence', + title: 'Longest Increasing Sequence', }, { id: '2431', contest_id: 'JAGSummer2012-day2', - problem_index: '2431', + problem_index: 'H', name: 'House Moving', - title: '2431. House Moving', + title: 'House Moving', }, { id: '2432', contest_id: 'JAGSummer2012-day2', - problem_index: '2432', + problem_index: 'I', name: 'Sports Days 2.0', - title: '2432. Sports Days 2.0', + title: 'Sports Days 2.0', }, { id: '2433', contest_id: 'JAGSummer2012-day2', - problem_index: '2433', + problem_index: 'J', name: 'Final Defense Line', - title: '2433. Final Defense Line', + title: 'Final Defense Line', }, { id: '2434', contest_id: 'JAGSummer2012-day3A', - problem_index: '2434', + problem_index: 'A', name: 'Audition', - title: '2434. Audition', + title: 'Audition', }, { id: '2435', contest_id: 'JAGSummer2012-day3A', - problem_index: '2435', + problem_index: 'B', name: 'Zero Division Checker', - title: '2435. Zero Division Checker', + title: 'Zero Division Checker', }, { id: '2436', contest_id: 'JAGSummer2012-day3A', - problem_index: '2436', + problem_index: 'C', name: 'Card', - title: '2436. Card', + title: 'Card', }, { id: '2437', contest_id: 'JAGSummer2012-day3A', - problem_index: '2437', + problem_index: 'D', name: 'DNA', - title: '2437. DNA', + title: 'DNA', }, { id: '2438', contest_id: 'JAGSummer2012-day3A', - problem_index: '2438', + problem_index: 'E', name: 'YAML', - title: '2438. YAML', + title: 'YAML', }, { id: '2439', contest_id: 'JAGSummer2012-day3A', - problem_index: '2439', + problem_index: 'F', name: 'Hakone', - title: '2439. Hakone', + title: 'Hakone', }, { id: '2440', contest_id: 'JAGSummer2012-day3B', - problem_index: '2440', + problem_index: 'A', name: 'Kagisys', - title: '2440. Kagisys', + title: 'Kagisys', }, { id: '2441', contest_id: 'JAGSummer2012-day3B', - problem_index: '2441', + problem_index: 'B', name: 'FizzBuzz', - title: '2441. FizzBuzz', + title: 'FizzBuzz', }, { id: '2442', contest_id: 'JAGSummer2012-day3B', - problem_index: '2442', + problem_index: 'C', name: 'ConvexCut', - title: '2442. ConvexCut', + title: 'ConvexCut', }, { id: '2443', contest_id: 'JAGSummer2012-day3B', - problem_index: '2443', + problem_index: 'D', name: 'ReverseSort', - title: '2443. ReverseSort', + title: 'ReverseSort', }, { id: '2444', contest_id: 'JAGSummer2012-day3B', - problem_index: '2444', + problem_index: 'E', name: 'Substring', - title: '2444. Substring', + title: 'Substring', }, { id: '2445', contest_id: 'JAGSummer2012-day3B', - problem_index: '2445', + problem_index: 'F', name: 'MinimumCostPath', - title: '2445. MinimumCostPath', + title: 'MinimumCostPath', }, { id: '2446', contest_id: 'JAGSummer2012-day3B', - problem_index: '2446', + problem_index: 'G', name: 'Enumeration', - title: '2446. Enumeration', + title: 'Enumeration', }, { id: '2447', contest_id: 'JAGSummer2012-day4', - problem_index: '2447', + problem_index: 'A', name: 'A Two Floors Dungeon', - title: '2447. A Two Floors Dungeon', + title: 'A Two Floors Dungeon', }, { id: '2448', contest_id: 'JAGSummer2012-day4', - problem_index: '2448', + problem_index: 'B', name: 'Area Folding', - title: '2448. Area Folding', + title: 'Area Folding', }, { id: '2449', contest_id: 'JAGSummer2012-day4', - problem_index: '2449', + problem_index: 'C', name: 'Connect', - title: '2449. Connect', + title: 'Connect', }, { id: '2450', contest_id: 'JAGSummer2012-day4', - problem_index: '2450', + problem_index: 'D', name: 'Do use segment tree', - title: '2450. Do use segment tree', + title: 'Do use segment tree', }, { id: '2451', contest_id: 'JAGSummer2012-day4', - problem_index: '2451', + problem_index: 'E', name: 'Move on Dice', - title: '2451. Move on Dice', + title: 'Move on Dice', }, { id: '2452', contest_id: 'JAGSummer2012-day4', - problem_index: '2452', + problem_index: 'F', name: 'Pipeline Plans', - title: '2452. Pipeline Plans', + title: 'Pipeline Plans', }, { id: '2453', contest_id: 'JAGSummer2012-day4', - problem_index: '2453', + problem_index: 'G', name: 'Presentation', - title: '2453. Presentation', + title: 'Presentation', }, { id: '2454', contest_id: 'JAGSummer2012-day4', - problem_index: '2454', + problem_index: 'H', name: 'Repairing', - title: '2454. Repairing', + title: 'Repairing', }, { id: '2455', contest_id: 'JAGSummer2012-day4', - problem_index: '2455', + problem_index: 'I', name: 'Sun and Moon', - title: '2455. Sun and Moon', + title: 'Sun and Moon', }, { id: '2456', contest_id: 'JAGSummer2012-day4', - problem_index: '2456', + problem_index: 'J', name: 'Usoperanto', - title: '2456. Usoperanto', + title: 'Usoperanto', }, { id: '2600', contest_id: 'JAGSummer2013-day2', - problem_index: '2600', + problem_index: 'A', name: 'Koto Distance', - title: '2600. Koto Distance', + title: 'Koto Distance', }, { id: '2601', contest_id: 'JAGSummer2013-day2', - problem_index: '2601', + problem_index: 'B', name: 'Evacuation Route', - title: '2601. Evacuation Route', + title: 'Evacuation Route', }, { id: '2602', contest_id: 'JAGSummer2013-day2', - problem_index: '2602', + problem_index: 'C', name: 'Apples', - title: '2602. Apples', + title: 'Apples', }, { id: '2603', contest_id: 'JAGSummer2013-day2', - problem_index: '2603', + problem_index: 'D', name: 'TiMe Table', - title: '2603. TiMe Table', + title: 'TiMe Table', }, { id: '2604', contest_id: 'JAGSummer2013-day2', - problem_index: '2604', + problem_index: 'E', name: 'Pattern Language', - title: '2604. Pattern Language', + title: 'Pattern Language', }, { id: '2605', contest_id: 'JAGSummer2013-day2', - problem_index: '2605', + problem_index: 'F', name: 'Social Monsters', - title: '2605. Social Monsters', + title: 'Social Monsters', }, { id: '2606', contest_id: 'JAGSummer2013-day2', - problem_index: '2606', + problem_index: 'G', name: 'Perm Query', - title: '2606. Perm Query', + title: 'Perm Query', }, { id: '2607', contest_id: 'JAGSummer2013-day3', - problem_index: '2607', + problem_index: 'A', name: 'Invest Master', - title: '2607. Invest Master', + title: 'Invest Master', }, { id: '2608', contest_id: 'JAGSummer2013-day3', - problem_index: '2608', + problem_index: 'B', name: 'Minus One', - title: '2608. Minus One', + title: 'Minus One', }, { id: '2609', contest_id: 'JAGSummer2013-day3', - problem_index: '2609', + problem_index: 'C', name: 'Wave Attack', - title: '2609. Wave Attack', + title: 'Wave Attack', }, { id: '2610', contest_id: 'JAGSummer2013-day3', - problem_index: '2610', + problem_index: 'D', name: 'Fast Division', - title: '2610. Fast Division', + title: 'Fast Division', }, { id: '2611', contest_id: 'JAGSummer2013-day3', - problem_index: '2611', + problem_index: 'E', name: 'Ordering', - title: '2611. Ordering', + title: 'Ordering', }, { id: '2612', contest_id: 'JAGSummer2013-day3', - problem_index: '2612', + problem_index: 'F', name: 'Phutball', - title: '2612. Phutball', + title: 'Phutball', }, { id: '2613', contest_id: 'JAGSummer2013-day3', - problem_index: '2613', + problem_index: 'G', name: 'Unordered Operators', - title: '2613. Unordered Operators', + title: 'Unordered Operators', }, { id: '2614', contest_id: 'JAGSummer2013-day3', - problem_index: '2614', + problem_index: 'H', name: 'Almost Same Substring', - title: '2614. Almost Same Substring', + title: 'Almost Same Substring', }, { id: '2615', contest_id: 'JAGSummer2013-day3', - problem_index: '2615', + problem_index: 'I', name: 'A + B', - title: '2615. A + B', + title: 'A + B', }, { id: '2616', contest_id: 'JAGSummer2013-day3', - problem_index: '2616', + problem_index: 'J', name: 'KuruKuruKururin', - title: '2616. KuruKuruKururin', + title: 'KuruKuruKururin', }, { id: '2617', contest_id: 'JAGSummer2013-day3', - problem_index: '2617', + problem_index: 'K', name: 'Air Pollution', - title: '2617. Air Pollution', + title: 'Air Pollution', }, { id: '2618', contest_id: 'JAGSummer2013-day3', - problem_index: '2618', + problem_index: 'L', name: 'Trip to Kyoto', - title: '2618. Trip to Kyoto', + title: 'Trip to Kyoto', }, { id: '2619', contest_id: 'JAGSummer2013-day4', - problem_index: '2619', + problem_index: 'A', name: 'Thread Tree', - title: '2619. Thread Tree', + title: 'Thread Tree', }, { id: '2620', contest_id: 'JAGSummer2013-day4', - problem_index: '2620', + problem_index: 'B', name: 'Trodden Cable', - title: '2620. Trodden Cable', + title: 'Trodden Cable', }, { id: '2621', contest_id: 'JAGSummer2013-day4', - problem_index: '2621', + problem_index: 'C', name: 'Fox Observation', - title: '2621. Fox Observation', + title: 'Fox Observation', }, { id: '2622', contest_id: 'JAGSummer2013-day4', - problem_index: '2622', + problem_index: 'D', name: 'Removing Magical Tiles', - title: '2622. Removing Magical Tiles', + title: 'Removing Magical Tiles', }, { id: '2623', contest_id: 'JAGSummer2013-day4', - problem_index: '2623', + problem_index: 'E', name: 'Optimal alpha beta pruning', - title: '2623. Optimal alpha beta pruning', + title: 'Optimal alpha beta pruning', }, { id: '2624', contest_id: 'JAGSummer2013-day4', - problem_index: '2624', + problem_index: 'F', name: 'Graph Automata Player', - title: '2624. Graph Automata Player', + title: 'Graph Automata Player', }, { id: '2625', contest_id: 'JAGSummer2013-day4', - problem_index: '2625', + problem_index: 'G', name: 'Spotlight Movement', - title: '2625. Spotlight Movement', + title: 'Spotlight Movement', }, { id: '2626', contest_id: 'JAGSummer2013-day4', - problem_index: '2626', + problem_index: 'H', name: 'Gravity Point', - title: '2626. Gravity Point', + title: 'Gravity Point', }, { id: '2627', contest_id: 'JAGSummer2013-day4', - problem_index: '2627', + problem_index: 'I', name: 'Multi Path Story', - title: '2627. Multi Path Story', + title: 'Multi Path Story', }, { id: '2628', contest_id: 'JAGSummer2013-day4', - problem_index: '2628', + problem_index: 'J', name: 'Rotation Game', - title: '2628. Rotation Game', + title: 'Rotation Game', }, { id: '2629', contest_id: 'JAGSummer2014-day2', - problem_index: '2629', + problem_index: 'A', name: 'Manhattan', - title: '2629. Manhattan', + title: 'Manhattan', }, { id: '2630', contest_id: 'JAGSummer2014-day2', - problem_index: '2630', + problem_index: 'B', name: 'Dictionary', - title: '2630. Dictionary', + title: 'Dictionary', }, { id: '2631', contest_id: 'JAGSummer2014-day2', - problem_index: '2631', + problem_index: 'C', name: 'Clique Coloring', - title: '2631. Clique Coloring', + title: 'Clique Coloring', }, { id: '2632', contest_id: 'JAGSummer2014-day2', - problem_index: '2632', + problem_index: 'D', name: 'Dense Amidakuji', - title: '2632. Dense Amidakuji', + title: 'Dense Amidakuji', }, { id: '2633', contest_id: 'JAGSummer2014-day2', - problem_index: '2633', + problem_index: 'E', name: 'Cellular Automaton', - title: '2633. Cellular Automaton', + title: 'Cellular Automaton', }, { id: '2634', contest_id: 'JAGSummer2014-day2', - problem_index: '2634', + problem_index: 'F', name: 'Directions', - title: '2634. Directions', + title: 'Directions', }, { id: '2635', contest_id: 'JAGSummer2014-day2', - problem_index: '2635', + problem_index: 'G', name: 'Snake', - title: '2635. Snake', + title: 'Snake', }, { id: '2636', contest_id: 'JAGSummer2014-day2', - problem_index: '2636', + problem_index: 'H', name: 'Distance Sum', - title: '2636. Distance Sum', + title: 'Distance Sum', }, { id: '2637', contest_id: 'JAGSummer2014-day2', - problem_index: '2637', + problem_index: 'I', name: 'Substring Pairs', - title: '2637. Substring Pairs', + title: 'Substring Pairs', }, { id: '2638', contest_id: 'JAGSummer2014-day2', - problem_index: '2638', + problem_index: 'J', name: 'Hyperrectangle', - title: '2638. Hyperrectangle', + title: 'Hyperrectangle', }, { id: '2639', contest_id: 'JAGSummer2014-day4', - problem_index: '2639', + problem_index: 'A', name: 'Yamanote Line', - title: '2639. Yamanote Line', + title: 'Yamanote Line', }, { id: '2640', contest_id: 'JAGSummer2014-day4', - problem_index: '2640', + problem_index: 'B', name: 'Prowler', - title: '2640. Prowler', + title: 'Prowler', }, { id: '2641', contest_id: 'JAGSummer2014-day4', - problem_index: '2641', + problem_index: 'C', name: 'Magic Bullet', - title: '2641. Magic Bullet', + title: 'Magic Bullet', }, { id: '2642', contest_id: 'JAGSummer2014-day4', - problem_index: '2642', + problem_index: 'D', name: 'Dinner', - title: '2642. Dinner', + title: 'Dinner', }, { id: '2643', contest_id: 'JAGSummer2014-day4', - problem_index: '2643', + problem_index: 'E', name: 'AI', - title: '2643. AI', + title: 'AI', }, { id: '2644', contest_id: 'JAGSummer2014-day4', - problem_index: '2644', + problem_index: 'F', name: 'Longest Match', - title: '2644. Longest Match', + title: 'Longest Match', }, { id: '2645', contest_id: 'JAGSummer2014-day4', - problem_index: '2645', + problem_index: 'G', name: 'Recycle', - title: '2645. Recycle', + title: 'Recycle', }, { id: '2646', contest_id: 'JAGSummer2014-day4', - problem_index: '2646', + problem_index: 'H', name: 'Tournament', - title: '2646. Tournament', + title: 'Tournament', }, { id: '2647', contest_id: 'JAGSummer2014-day4', - problem_index: '2647', + problem_index: 'I', name: 'The Capital', - title: '2647. The Capital', + title: 'The Capital', }, { id: '2648', contest_id: 'JAGSummer2014-day4', - problem_index: '2648', + problem_index: 'J', name: 'Vongress', - title: '2648. Vongress', + title: 'Vongress', }, { id: '2706', contest_id: 'JAGSummer2015-day2', - problem_index: '2706', + problem_index: 'A', name: "Let's Solve Geometric Problems", - title: "2706. Let's Solve Geometric Problems", + title: "Let's Solve Geometric Problems", }, { id: '2707', contest_id: 'JAGSummer2015-day2', - problem_index: '2707', + problem_index: 'B', name: 'Jail', - title: '2707. Jail', + title: 'Jail', }, { id: '2708', contest_id: 'JAGSummer2015-day2', - problem_index: '2708', + problem_index: 'C', name: 'ABC Gene', - title: '2708. ABC Gene', + title: 'ABC Gene', }, { id: '2709', contest_id: 'JAGSummer2015-day2', - problem_index: '2709', + problem_index: 'D', name: 'Dark Room', - title: '2709. Dark Room', + title: 'Dark Room', }, { id: '2710', contest_id: 'JAGSummer2015-day2', - problem_index: '2710', + problem_index: 'E', name: 'An Equation in a Mine', - title: '2710. An Equation in a Mine', + title: 'An Equation in a Mine', }, { id: '2711', contest_id: 'JAGSummer2015-day2', - problem_index: '2711', + problem_index: 'F', name: 'Nearly Cyclic String', - title: '2711. Nearly Cyclic String', + title: 'Nearly Cyclic String', }, { id: '2712', contest_id: 'JAGSummer2015-day2', - problem_index: '2712', + problem_index: 'G', name: 'Escape', - title: '2712. Escape', + title: 'Escape', }, { id: '2713', contest_id: 'JAGSummer2015-day2', - problem_index: '2713', + problem_index: 'H', name: 'Bit Operation Game', - title: '2713. Bit Operation Game', + title: 'Bit Operation Game', }, { id: '2714', contest_id: 'JAGSummer2015-day2', - problem_index: '2714', + problem_index: 'I', name: 'Twin Reverse', - title: '2714. Twin Reverse', + title: 'Twin Reverse', }, { id: '2715', contest_id: 'JAGSummer2015-day2', - problem_index: '2715', + problem_index: 'J', name: 'Connection', - title: '2715. Connection', + title: 'Connection', }, { id: '2716', contest_id: 'JAGSummer2015-day2', - problem_index: '2716', + problem_index: 'K', name: 'Leapfrog', - title: '2716. Leapfrog', + title: 'Leapfrog', }, { id: '2717', contest_id: 'JAGSummer2015-day4', - problem_index: '2717', + problem_index: 'A', name: 'Where is the Boundary', - title: '2717. Where is the Boundary', + title: 'Where is the Boundary', }, { id: '2718', contest_id: 'JAGSummer2015-day4', - problem_index: '2718', + problem_index: 'B', name: 'Vector Field', - title: '2718. Vector Field', + title: 'Vector Field', }, { id: '2719', contest_id: 'JAGSummer2015-day4', - problem_index: '2719', + problem_index: 'C', name: 'Kuru Kuru Sushi', - title: '2719. Kuru Kuru Sushi', + title: 'Kuru Kuru Sushi', }, { id: '2720', contest_id: 'JAGSummer2015-day4', - problem_index: '2720', + problem_index: 'D', name: 'Identity Function', - title: '2720. Identity Function', + title: 'Identity Function', }, { id: '2721', contest_id: 'JAGSummer2015-day4', - problem_index: '2721', + problem_index: 'E', name: 'Enclose Points', - title: '2721. Enclose Points', + title: 'Enclose Points', }, { id: '2722', contest_id: 'JAGSummer2015-day4', - problem_index: '2722', + problem_index: 'F', name: 'Marching Course', - title: '2722. Marching Course', + title: 'Marching Course', }, { id: '2723', contest_id: 'JAGSummer2015-day4', - problem_index: '2723', + problem_index: 'G', name: 'Surface Area of Cubes', - title: '2723. Surface Area of Cubes', + title: 'Surface Area of Cubes', }, { id: '2724', contest_id: 'JAGSummer2015-day4', - problem_index: '2724', + problem_index: 'H', name: 'Laser Cutter', - title: '2724. Laser Cutter', + title: 'Laser Cutter', }, { id: '2725', contest_id: 'JAGSummer2015-day4', - problem_index: '2725', + problem_index: 'I', name: 'Live Programming', - title: '2725. Live Programming', + title: 'Live Programming', }, { id: '2726', contest_id: 'JAGSummer2015-day4', - problem_index: '2726', + problem_index: 'J', name: 'Black Company', - title: '2726. Black Company', + title: 'Black Company', }, { id: '2845', contest_id: 'JAGSummer2017-day3', - problem_index: '2845', + problem_index: 'A', name: 'Star in Parentheses', - title: '2845. Star in Parentheses', + title: 'Star in Parentheses', }, { id: '2846', contest_id: 'JAGSummer2017-day3', - problem_index: '2846', + problem_index: 'B', name: 'Slimming Plan', - title: '2846. Slimming Plan', + title: 'Slimming Plan', }, { id: '2847', contest_id: 'JAGSummer2017-day3', - problem_index: '2847', + problem_index: 'C', name: 'Ninja Map', - title: '2847. Ninja Map', + title: 'Ninja Map', }, { id: '2848', contest_id: 'JAGSummer2017-day3', - problem_index: '2848', + problem_index: 'D', name: 'Janken Master', - title: '2848. Janken Master', + title: 'Janken Master', }, { id: '2849', contest_id: 'JAGSummer2017-day3', - problem_index: '2849', + problem_index: 'E', name: 'Route Calculator', - title: '2849. Route Calculator', + title: 'Route Calculator', }, { id: '2850', contest_id: 'JAGSummer2017-day3', - problem_index: '2850', + problem_index: 'F', name: 'Endless BFS', - title: '2850. Endless BFS', + title: 'Endless BFS', }, { id: '2851', contest_id: 'JAGSummer2017-day3', - problem_index: '2851', + problem_index: 'G', name: 'Low Range-Sum Matrix', - title: '2851. Low Range-Sum Matrix', + title: 'Low Range-Sum Matrix', }, { id: '2852', contest_id: 'JAGSummer2017-day3', - problem_index: '2852', + problem_index: 'H', name: 'Tiny Room', - title: '2852. Tiny Room', + title: 'Tiny Room', }, { id: '2853', contest_id: 'JAGSummer2017-day3', - problem_index: '2853', + problem_index: 'I', name: "Librarian's Work", - title: "2853. Librarian's Work", + title: "Librarian's Work", }, { id: '2854', contest_id: 'JAGSummer2017-day3', - problem_index: '2854', + problem_index: 'J', name: 'Sum Source Detection', - title: '2854. Sum Source Detection', + title: 'Sum Source Detection', }, { id: '2855', contest_id: 'JAGSummer2017-day3', - problem_index: '2855', + problem_index: 'K', name: 'Permutation Period', - title: '2855. Permutation Period', + title: 'Permutation Period', }, { id: '2904', contest_id: 'JAGSummer2018-day3', - problem_index: '2904', + problem_index: 'A', name: 'GuruGuru', - title: '2904. GuruGuru', + title: 'GuruGuru', }, { id: '2905', contest_id: 'JAGSummer2018-day3', - problem_index: '2905', + problem_index: 'B', name: 'Colorful Drink', - title: '2905. Colorful Drink', + title: 'Colorful Drink', }, { id: '2906', contest_id: 'JAGSummer2018-day3', - problem_index: '2906', + problem_index: 'C', name: "Santa's Gift", - title: "2906. Santa's Gift", + title: "Santa's Gift", }, { id: '2907', contest_id: 'JAGSummer2018-day3', - problem_index: '2907', + problem_index: 'D', name: 'Prefix Suffix Search', - title: '2907. Prefix Suffix Search', + title: 'Prefix Suffix Search', }, { id: '2908', contest_id: 'JAGSummer2018-day3', - problem_index: '2908', + problem_index: 'E', name: 'Magic Triangles', - title: '2908. Magic Triangles', + title: 'Magic Triangles', }, { id: '2909', contest_id: 'JAGSummer2018-day3', - problem_index: '2909', + problem_index: 'F', name: 'Nim without Zero', - title: '2909. Nim without Zero', + title: 'Nim without Zero', }, { id: '2910', contest_id: 'JAGSummer2018-day3', - problem_index: '2910', + problem_index: 'G', name: 'Additions', - title: '2910. Additions', + title: 'Additions', }, { id: '2911', contest_id: 'JAGSummer2018-day3', - problem_index: '2911', + problem_index: 'H', name: 'Enlarge Circles', - title: '2911. Enlarge Circles', + title: 'Enlarge Circles', }, { id: '2912', contest_id: 'JAGSummer2018-day3', - problem_index: '2912', + problem_index: 'I', name: 'Sum of QQ', - title: '2912. Sum of QQ', + title: 'Sum of QQ', }, { id: '2913', contest_id: 'JAGSummer2018-day3', - problem_index: '2913', + problem_index: 'J', name: 'Prime Routing', - title: '2913. Prime Routing', + title: 'Prime Routing', }, { id: '2914', contest_id: 'JAGSummer2018-day3', - problem_index: '2914', + problem_index: 'K', name: 'Rough Sorting', - title: '2914. Rough Sorting', + title: 'Rough Sorting', }, { id: '2967', contest_id: 'JAGSummer2019-day1', - problem_index: '2967', + problem_index: 'A', name: 'AIGo', - title: '2967. AIGo', + title: 'AIGo', }, { id: '2968', contest_id: 'JAGSummer2019-day1', - problem_index: '2968', + problem_index: 'B', name: 'Non-trivial Common Divisor', - title: '2968. Non-trivial Common Divisor', + title: 'Non-trivial Common Divisor', }, { id: '2969', contest_id: 'JAGSummer2019-day1', - problem_index: '2969', + problem_index: 'C', name: 'Universal and Existential Quantifiers', - title: '2969. Universal and Existential Quantifiers', + title: 'Universal and Existential Quantifiers', }, { id: '2970', contest_id: 'JAGSummer2019-day1', - problem_index: '2970', + problem_index: 'D', name: 'Permutation Sort', - title: '2970. Permutation Sort', + title: 'Permutation Sort', }, { id: '2971', contest_id: 'JAGSummer2019-day1', - problem_index: '2971', + problem_index: 'E', name: 'Consistent Trading', - title: '2971. Consistent Trading', + title: 'Consistent Trading', }, { id: '2972', contest_id: 'JAGSummer2019-day1', - problem_index: '2972', + problem_index: 'F', name: 'All your base are belong to us', - title: '2972. All your base are belong to us', + title: 'All your base are belong to us', }, { id: '2973', contest_id: 'JAGSummer2019-day1', - problem_index: '2973', + problem_index: 'G', name: 'Route Calculator Returns', - title: '2973. Route Calculator Returns', + title: 'Route Calculator Returns', }, { id: '2974', contest_id: 'JAGSummer2019-day1', - problem_index: '2974', + problem_index: 'H', name: 'N-by-M grid calculation', - title: '2974. N-by-M grid calculation', + title: 'N-by-M grid calculation', }, { id: '2975', contest_id: 'JAGSummer2019-day1', - problem_index: '2975', + problem_index: 'I', name: 'Zombie Land', - title: '2975. Zombie Land', + title: 'Zombie Land', }, { id: '2976', contest_id: 'JAGSummer2019-day1', - problem_index: '2976', + problem_index: 'J', name: 'Rooks Game', - title: '2976. Rooks Game', + title: 'Rooks Game', }, { id: '2977', contest_id: 'JAGSummer2019-day1', - problem_index: '2977', + problem_index: 'K', name: 'Bombing', - title: '2977. Bombing', + title: 'Bombing', }, { id: '2978', contest_id: 'JAGSummer2019-day2', - problem_index: '2978', + problem_index: 'A', name: 'Undo Swapping', - title: '2978. Undo Swapping', + title: 'Undo Swapping', }, { id: '2979', contest_id: 'JAGSummer2019-day2', - problem_index: '2979', + problem_index: 'B', name: 'Spent Fuel Disposal', - title: '2979. Spent Fuel Disposal', + title: 'Spent Fuel Disposal', }, { id: '2980', contest_id: 'JAGSummer2019-day2', - problem_index: '2980', + problem_index: 'C', name: 'Cyclic String', - title: '2980. Cyclic String', + title: 'Cyclic String', }, { id: '2981', contest_id: 'JAGSummer2019-day2', - problem_index: '2981', + problem_index: 'D', name: 'Nose And Lung', - title: '2981. Nose And Lung', + title: 'Nose And Lung', }, { id: '2982', contest_id: 'JAGSummer2019-day2', - problem_index: '2982', + problem_index: 'E', name: 'Expensive Function', - title: '2982. Expensive Function', + title: 'Expensive Function', }, { id: '2983', contest_id: 'JAGSummer2019-day2', - problem_index: '2983', + problem_index: 'F', name: 'Clique Drawing', - title: '2983. Clique Drawing', + title: 'Clique Drawing', }, { id: '2984', contest_id: 'JAGSummer2019-day2', - problem_index: '2984', + problem_index: 'G', name: 'Human Seperation', - title: '2984. Human Seperation', + title: 'Human Seperation', }, { id: '2985', contest_id: 'JAGSummer2019-day2', - problem_index: '2985', + problem_index: 'H', name: 'Permutation Score', - title: '2985. Permutation Score', + title: 'Permutation Score', }, { id: '2986', contest_id: 'JAGSummer2019-day2', - problem_index: '2986', + problem_index: 'I', name: 'Palindrome Compliment', - title: '2986. Palindrome Compliment', + title: 'Palindrome Compliment', }, { id: '2987', contest_id: 'JAGSummer2019-day2', - problem_index: '2987', + problem_index: 'J', name: 'Prefix Suffix Nim', - title: '2987. Prefix Suffix Nim', + title: 'Prefix Suffix Nim', }, { id: '2988', contest_id: 'JAGSummer2019-day3', - problem_index: '2988', + problem_index: 'A', name: 'Union Ball', - title: '2988. Union Ball', + title: 'Union Ball', }, { id: '2989', contest_id: 'JAGSummer2019-day3', - problem_index: '2989', + problem_index: 'B', name: 'AddMulSubDiv', - title: '2989. AddMulSubDiv', + title: 'AddMulSubDiv', }, { id: '2990', contest_id: 'JAGSummer2019-day3', - problem_index: '2990', + problem_index: 'C', name: 'Shuttle Run', - title: '2990. Shuttle Run', + title: 'Shuttle Run', }, { id: '2991', contest_id: 'JAGSummer2019-day3', - problem_index: '2991', + problem_index: 'D', name: 'XORANDORBAN', - title: '2991. XORANDORBAN', + title: 'XORANDORBAN', }, { id: '2992', contest_id: 'JAGSummer2019-day3', - problem_index: '2992', + problem_index: 'E', name: 'Red Black Balloons', - title: '2992. Red Black Balloons', + title: 'Red Black Balloons', }, { id: '2993', contest_id: 'JAGSummer2019-day3', - problem_index: '2993', + problem_index: 'F', name: 'Invariant Tree', - title: '2993. Invariant Tree', + title: 'Invariant Tree', }, { id: '2994', contest_id: 'JAGSummer2019-day3', - problem_index: '2994', + problem_index: 'G', name: 'Toss Cut Tree', - title: '2994. Toss Cut Tree', + title: 'Toss Cut Tree', }, { id: '2995', contest_id: 'JAGSummer2019-day3', - problem_index: '2995', + problem_index: 'H', name: 'Colorful Tree', - title: '2995. Colorful Tree', + title: 'Colorful Tree', }, { id: '2996', contest_id: 'JAGSummer2019-day3', - problem_index: '2996', + problem_index: 'I', name: 'Add', - title: '2996. Add', + title: 'Add', }, { id: '2997', contest_id: 'JAGSummer2019-day3', - problem_index: '2997', + problem_index: 'J', name: 'Horizontal-Vertical Permutation', - title: '2997. Horizontal-Vertical Permutation', + title: 'Horizontal-Vertical Permutation', }, { id: '3600', contest_id: 'JAGSummer2023-day2', - problem_index: '3600', + problem_index: 'A', name: 'Sum of Product of Binomial Coefficients', - title: '3600. Sum of Product of Binomial Coefficients', + title: 'Sum of Product of Binomial Coefficients', }, { id: '3601', contest_id: 'JAGSummer2023-day2', - problem_index: '3601', + problem_index: 'B', name: 'Mercurialist', - title: '3601. Mercurialist', + title: 'Mercurialist', }, { id: '3602', contest_id: 'JAGSummer2023-day2', - problem_index: '3602', + problem_index: 'C', name: 'Umbrella Queries', - title: '3602. Umbrella Queries', + title: 'Umbrella Queries', }, { id: '3603', contest_id: 'JAGSummer2023-day2', - problem_index: '3603', + problem_index: 'D', name: 'Gemini Tree (Ver.Jadeite)', - title: '3603. Gemini Tree (Ver.Jadeite)', + title: 'Gemini Tree (Ver.Jadeite)', }, { id: '3604', contest_id: 'JAGSummer2023-day2', - problem_index: '3604', + problem_index: 'E', name: 'Gemini Tree (Ver.Lapislazuli)', - title: '3604. Gemini Tree (Ver.Lapislazuli)', + title: 'Gemini Tree (Ver.Lapislazuli)', }, { id: '3605', contest_id: 'JAGSummer2023-day2', - problem_index: '3605', + problem_index: 'F', name: 'Tea time in the grand garden', - title: '3605. Tea time in the grand garden', + title: 'Tea time in the grand garden', }, { id: '3606', contest_id: 'JAGSummer2023-day2', - problem_index: '3606', + problem_index: 'G', name: 'Fraises dans une boite', - title: '3606. Fraises dans une boite', + title: 'Fraises dans une boite', }, { id: '3607', contest_id: 'JAGSummer2023-day2', - problem_index: '3607', + problem_index: 'H', name: 'Empty Quartz', - title: '3607. Empty Quartz', + title: 'Empty Quartz', }, { id: '3608', contest_id: 'JAGSummer2023-day2', - problem_index: '3608', + problem_index: 'I', name: 'Distance Permutation', - title: '3608. Distance Permutation', + title: 'Distance Permutation', }, { id: '3609', contest_id: 'JAGSummer2023-day2', - problem_index: '3609', + problem_index: 'J', name: 'Knight Game', - title: '3609. Knight Game', + title: 'Knight Game', }, { id: '3610', contest_id: 'JAGSummer2023-day2', - problem_index: '3610', + problem_index: 'K', name: 'Drifting', - title: '3610. Drifting', + title: 'Drifting', }, { id: '3611', contest_id: 'JAGSummer2023-day2', - problem_index: '3611', + problem_index: 'L', name: 'Disjoint-Sparse-Table Optimization', - title: '3611. Disjoint-Sparse-Table Optimization', + title: 'Disjoint-Sparse-Table Optimization', }, { id: '3612', contest_id: 'JAGSummer2023-day3', - problem_index: '3612', + problem_index: 'A', name: 'Roller Coaster', - title: '3612. Roller Coaster', + title: 'Roller Coaster', }, { id: '3613', contest_id: 'JAGSummer2023-day3', - problem_index: '3613', + problem_index: 'B', name: 'Break a Prison', - title: '3613. Break a Prison', + title: 'Break a Prison', }, { id: '3614', contest_id: 'JAGSummer2023-day3', - problem_index: '3614', + problem_index: 'C', name: 'Camp room assignment', - title: '3614. Camp room assignment', + title: 'Camp room assignment', }, { id: '3615', contest_id: 'JAGSummer2023-day3', - problem_index: '3615', + problem_index: 'D', name: 'Many-hued Tree', - title: '3615. Many-hued Tree', + title: 'Many-hued Tree', }, { id: '3616', contest_id: 'JAGSummer2023-day3', - problem_index: '3616', + problem_index: 'E', name: 'Gacha 101', - title: '3616. Gacha 101', + title: 'Gacha 101', }, { id: '3617', contest_id: 'JAGSummer2023-day3', - problem_index: '3617', + problem_index: 'F', name: 'Digit-only subrectangles', - title: '3617. Digit-only subrectangles', + title: 'Digit-only subrectangles', }, { id: '3618', contest_id: 'JAGSummer2023-day3', - problem_index: '3618', + problem_index: 'G', name: 'Convex Polygon MST', - title: '3618. Convex Polygon MST', + title: 'Convex Polygon MST', }, { id: '3619', contest_id: 'JAGSummer2023-day3', - problem_index: '3619', + problem_index: 'H', name: 'LCP Queries', - title: '3619. LCP Queries', + title: 'LCP Queries', }, { id: '3620', contest_id: 'JAGSummer2023-day3', - problem_index: '3620', + problem_index: 'I', name: 'Best parentheses', - title: '3620. Best parentheses', + title: 'Best parentheses', }, { id: '3621', contest_id: 'JAGSummer2023-day3', - problem_index: '3621', + problem_index: 'J', name: 'Edit distance on table', - title: '3621. Edit distance on table', + title: 'Edit distance on table', }, { id: '3622', contest_id: 'JAGSummer2023-day3', - problem_index: '3622', + problem_index: 'K', name: 'Odd trip plans', - title: '3622. Odd trip plans', + title: 'Odd trip plans', }, { id: '3623', contest_id: 'JAGSummer2024-day2', - problem_index: '3623', + problem_index: 'A', name: 'Add Add Add', - title: '3623. Add Add Add', + title: 'Add Add Add', }, { id: '3624', contest_id: 'JAGSummer2024-day2', - problem_index: '3624', + problem_index: 'B', name: 'Broken Parentheses', - title: '3624. Broken Parentheses', + title: 'Broken Parentheses', }, { id: '3625', contest_id: 'JAGSummer2024-day2', - problem_index: '3625', + problem_index: 'C', name: 'Convenient Banknotes', - title: '3625. Convenient Banknotes', + title: 'Convenient Banknotes', }, { id: '3626', contest_id: 'JAGSummer2024-day2', - problem_index: '3626', + problem_index: 'D', name: 'Do Make Segment Tree', - title: '3626. Do Make Segment Tree', + title: 'Do Make Segment Tree', }, { id: '3627', contest_id: 'JAGSummer2024-day2', - problem_index: '3627', + problem_index: 'E', name: 'Expression Sum', - title: '3627. Expression Sum', + title: 'Expression Sum', }, { id: '3628', contest_id: 'JAGSummer2024-day2', - problem_index: '3628', + problem_index: 'F', name: 'Flip Path on Rooted Tree', - title: '3628. Flip Path on Rooted Tree', + title: 'Flip Path on Rooted Tree', }, { id: '3629', contest_id: 'JAGSummer2024-day2', - problem_index: '3629', + problem_index: 'G', name: 'Give Me a Lot of Triangles', - title: '3629. Give Me a Lot of Triangles', + title: 'Give Me a Lot of Triangles', }, { id: '3630', contest_id: 'JAGSummer2024-day2', - problem_index: '3630', + problem_index: 'H', name: 'Half Plane Painting', - title: '3630. Half Plane Painting', + title: 'Half Plane Painting', }, { id: '3631', contest_id: 'JAGSummer2024-day2', - problem_index: '3631', + problem_index: 'I', name: 'I Love Square Number', - title: '3631. I Love Square Number', + title: 'I Love Square Number', }, { id: '3632', contest_id: 'JAGSummer2024-day2', - problem_index: '3632', + problem_index: 'J', name: 'Just Believe in Binary Search', - title: '3632. Just Believe in Binary Search', + title: 'Just Believe in Binary Search', }, { id: '3633', contest_id: 'JAGSummer2024-day2', - problem_index: '3633', + problem_index: 'K', name: 'K-th Nondivisor', - title: '3633. K-th Nondivisor', + title: 'K-th Nondivisor', }, { id: '3634', contest_id: 'JAGSummer2024-day2', - problem_index: '3634', + problem_index: 'L', name: 'Linear Time Inversion Number', - title: '3634. Linear Time Inversion Number', + title: 'Linear Time Inversion Number', }, { id: '3635', contest_id: 'JAGSummer2024-day2', - problem_index: '3635', + problem_index: 'M', name: 'Max Sum of GCD', - title: '3635. Max Sum of GCD', + title: 'Max Sum of GCD', }, { id: '3636', contest_id: 'JAGSummer2024-day2', - problem_index: '3636', + problem_index: 'N', name: 'Noncoprime Subsequences', - title: '3636. Noncoprime Subsequences', + title: 'Noncoprime Subsequences', }, { id: '3637', contest_id: 'JAGSummer2024-day3', - problem_index: '3637', + problem_index: 'A', name: 'Tower Defense', - title: '3637. Tower Defense', + title: 'Tower Defense', }, { id: '3638', contest_id: 'JAGSummer2024-day3', - problem_index: '3638', + problem_index: 'B', name: 'Renovation', - title: '3638. Renovation', + title: 'Renovation', }, { id: '3639', contest_id: 'JAGSummer2024-day3', - problem_index: '3639', + problem_index: 'C', name: 'Commutativity', - title: '3639. Commutativity', + title: 'Commutativity', }, { id: '3640', contest_id: 'JAGSummer2024-day3', - problem_index: '3640', + problem_index: 'D', name: 'Paper Cut Game', - title: '3640. Paper Cut Game', + title: 'Paper Cut Game', }, { id: '3641', contest_id: 'JAGSummer2024-day3', - problem_index: '3641', + problem_index: 'E', name: 'Ball Passing', - title: '3641. Ball Passing', + title: 'Ball Passing', }, { id: '3642', contest_id: 'JAGSummer2024-day3', - problem_index: '3642', + problem_index: 'F', name: 'Halfway Through the Book', - title: '3642. Halfway Through the Book', + title: 'Halfway Through the Book', }, { id: '3644', contest_id: 'JAGSummer2024-day3', - problem_index: '3644', + problem_index: 'H', name: 'Coins on a Tree', - title: '3644. Coins on a Tree', + title: 'Coins on a Tree', }, { id: '3645', contest_id: 'JAGSummer2024-day3', - problem_index: '3645', + problem_index: 'I', name: 'Fragile Tree', - title: '3645. Fragile Tree', + title: 'Fragile Tree', }, { id: '3646', contest_id: 'JAGSummer2024-day3', - problem_index: '3646', + problem_index: 'J', name: 'Draw the Tree', - title: '3646. Draw the Tree', + title: 'Draw the Tree', }, { id: '3647', contest_id: 'JAGSummer2024-day3', - problem_index: '3647', + problem_index: 'K', name: 'Equal or Not Equal', - title: '3647. Equal or Not Equal', + title: 'Equal or Not Equal', }, { id: '2073', contest_id: 'JAGWinter2006-day2', - problem_index: '2073', + problem_index: 'D', name: 'The Phantom', - title: '2073. The Phantom', + title: 'The Phantom', }, { id: '2074', contest_id: 'JAGWinter2006-day2', - problem_index: '2074', + problem_index: 'F', name: 'Rakunarok', - title: '2074. Rakunarok', + title: 'Rakunarok', }, { id: '2075', contest_id: 'JAGWinter2006-day3', - problem_index: '2075', + problem_index: 'A', name: 'Dock to the Future', - title: '2075. Dock to the Future', + title: 'Dock to the Future', }, { id: '2076', contest_id: 'JAGWinter2006-day3', - problem_index: '2076', + problem_index: 'D', name: 'Flame of Nucleus', - title: '2076. Flame of Nucleus', + title: 'Flame of Nucleus', }, { id: '2077', contest_id: 'JAGWinter2006-day3', - problem_index: '2077', + problem_index: 'G', name: 'Resource', - title: '2077. Resource', + title: 'Resource', }, { id: '2078', contest_id: 'JAGWinter2006-day3', - problem_index: '2078', + problem_index: 'H', name: 'Exact Arithmetic', - title: '2078. Exact Arithmetic', + title: 'Exact Arithmetic', }, { id: '2112', contest_id: 'JAGWinter2007-day2', - problem_index: '2112', + problem_index: 'A', name: 'Defend the Nation', - title: '2112. Defend the Nation', + title: 'Defend the Nation', }, { id: '2113', contest_id: 'JAGWinter2007-day2', - problem_index: '2113', + problem_index: 'B', name: 'Electrophoretic', - title: '2113. Electrophoretic', + title: 'Electrophoretic', }, { id: '2114', contest_id: 'JAGWinter2007-day2', - problem_index: '2114', + problem_index: 'C', name: 'Median Filter', - title: '2114. Median Filter', + title: 'Median Filter', }, { id: '2115', contest_id: 'JAGWinter2007-day2', - problem_index: '2115', + problem_index: 'D', name: 'Life Game', - title: '2115. Life Game', + title: 'Life Game', }, { id: '2116', contest_id: 'JAGWinter2007-day2', - problem_index: '2116', + problem_index: 'E', name: 'Subdividing a Land', - title: '2116. Subdividing a Land', + title: 'Subdividing a Land', }, { id: '2117', contest_id: 'JAGWinter2007-day2', - problem_index: '2117', + problem_index: 'F', name: 'Connect Line Segments', - title: '2117. Connect Line Segments', + title: 'Connect Line Segments', }, { id: '2118', contest_id: 'JAGWinter2007-day2', - problem_index: '2118', + problem_index: 'G', name: 'Oil Company', - title: '2118. Oil Company', + title: 'Oil Company', }, { id: '2119', contest_id: 'JAGWinter2007-day2', - problem_index: '2119', + problem_index: 'H', name: 'Finding the Top RPS Player', - title: '2119. Finding the Top RPS Player', + title: 'Finding the Top RPS Player', }, { id: '2120', contest_id: 'JAGWinter2007-day2', - problem_index: '2120', + problem_index: 'I', name: 'Revenge of Voronoi', - title: '2120. Revenge of Voronoi', + title: 'Revenge of Voronoi', }, { id: '2121', contest_id: 'JAGWinter2007-day2', - problem_index: '2121', + problem_index: 'J', name: 'Castle Wall', - title: '2121. Castle Wall', + title: 'Castle Wall', }, { id: '2122', contest_id: 'JAGWinter2007-day3', - problem_index: '2122', + problem_index: 'A', name: 'Optimization of Combinatorial Circuits', - title: '2122. Optimization of Combinatorial Circuits', + title: 'Optimization of Combinatorial Circuits', }, { id: '2123', contest_id: 'JAGWinter2007-day3', - problem_index: '2123', + problem_index: 'B', name: 'Divisor Function', - title: '2123. Divisor Function', + title: 'Divisor Function', }, { id: '2124', contest_id: 'JAGWinter2007-day3', - problem_index: '2124', + problem_index: 'C', name: 'Magical Dungeon', - title: '2124. Magical Dungeon', + title: 'Magical Dungeon', }, { id: '2125', contest_id: 'JAGWinter2007-day3', - problem_index: '2125', + problem_index: 'D', name: 'Land Mark', - title: '2125. Land Mark', + title: 'Land Mark', }, { id: '2126', contest_id: 'JAGWinter2007-day3', - problem_index: '2126', + problem_index: 'F', name: 'Alien Pianist', - title: '2126. Alien Pianist', + title: 'Alien Pianist', }, { id: '2127', contest_id: 'JAGWinter2007-day3', - problem_index: '2127', + problem_index: 'G', name: 'Japanese Style Pub', - title: '2127. Japanese Style Pub', + title: 'Japanese Style Pub', }, { id: '2128', contest_id: 'JAGWinter2007-day3', - problem_index: '2128', + problem_index: 'H', name: 'Two Rings', - title: '2128. Two Rings', + title: 'Two Rings', }, { id: '2129', contest_id: 'JAGWinter2007-day3', - problem_index: '2129', + problem_index: 'I', name: 'Text Justification', - title: '2129. Text Justification', + title: 'Text Justification', }, { id: '2130', contest_id: 'JAGWinter2007-day3', - problem_index: '2130', + problem_index: 'J', name: 'Billion Million Thousand', - title: '2130. Billion Million Thousand', + title: 'Billion Million Thousand', }, { id: '2457', contest_id: 'JAGWinter2008-day2', - problem_index: '2457', + problem_index: 'A', name: 'Adhoc Translation', - title: '2457. Adhoc Translation', + title: 'Adhoc Translation', }, { id: '2458', contest_id: 'JAGWinter2008-day2', - problem_index: '2458', + problem_index: 'B', name: 'Artistic Art Museum', - title: '2458. Artistic Art Museum', + title: 'Artistic Art Museum', }, { id: '2459', contest_id: 'JAGWinter2008-day2', - problem_index: '2459', + problem_index: 'C', name: 'Complex Integer Solutions', - title: '2459. Complex Integer Solutions', + title: 'Complex Integer Solutions', }, { id: '2460', contest_id: 'JAGWinter2008-day2', - problem_index: '2460', + problem_index: 'D', name: 'Dial Key', - title: '2460. Dial Key', + title: 'Dial Key', }, { id: '2461', contest_id: 'JAGWinter2008-day2', - problem_index: '2461', + problem_index: 'E', name: 'Dungeon Master', - title: '2461. Dungeon Master', + title: 'Dungeon Master', }, { id: '2462', contest_id: 'JAGWinter2008-day2', - problem_index: '2462', + problem_index: 'F', name: 'Exciting Bicycle', - title: '2462. Exciting Bicycle', + title: 'Exciting Bicycle', }, { id: '2463', contest_id: 'JAGWinter2008-day2', - problem_index: '2463', + problem_index: 'G', name: 'Fuel Problem', - title: '2463. Fuel Problem', + title: 'Fuel Problem', }, { id: '2464', contest_id: 'JAGWinter2008-day2', - problem_index: '2464', + problem_index: 'H', name: 'Magic Walls', - title: '2464. Magic Walls', + title: 'Magic Walls', }, { id: '2465', contest_id: 'JAGWinter2008-day2', - problem_index: '2465', + problem_index: 'I', name: 'Sort by Hand', - title: '2465. Sort by Hand', + title: 'Sort by Hand', }, { id: '2466', contest_id: 'JAGWinter2008-day2', - problem_index: '2466', + problem_index: 'J', name: 'Substring Expression', - title: '2466. Substring Expression', + title: 'Substring Expression', }, { id: '2467', contest_id: 'JAGWinter2008-day2', - problem_index: '2467', + problem_index: 'K', name: 'Up Above the World So High', - title: '2467. Up Above the World So High', + title: 'Up Above the World So High', }, { id: '2468', contest_id: 'JAGWinter2008-day3', - problem_index: '2468', + problem_index: 'A', name: 'Cache Control', - title: '2468. Cache Control', + title: 'Cache Control', }, { id: '2469', contest_id: 'JAGWinter2008-day3', - problem_index: '2469', + problem_index: 'B', name: 'Cover Time', - title: '2469. Cover Time', + title: 'Cover Time', }, { id: '2470', contest_id: 'JAGWinter2008-day3', - problem_index: '2470', + problem_index: 'C', name: 'Craftsman', - title: '2470. Craftsman', + title: 'Craftsman', }, { id: '2471', contest_id: 'JAGWinter2008-day3', - problem_index: '2471', + problem_index: 'D', name: 'Divide the Water', - title: '2471. Divide the Water', + title: 'Divide the Water', }, { id: '2472', contest_id: 'JAGWinter2008-day3', - problem_index: '2472', + problem_index: 'E', name: "Mickle's Beam", - title: "2472. Mickle's Beam", + title: "Mickle's Beam", }, { id: '2473', contest_id: 'JAGWinter2008-day3', - problem_index: '2473', + problem_index: 'F', name: 'Poor Computer', - title: '2473. Poor Computer', + title: 'Poor Computer', }, { id: '2474', contest_id: 'JAGWinter2008-day3', - problem_index: '2474', + problem_index: 'G', name: 'Riffle Swap', - title: '2474. Riffle Swap', + title: 'Riffle Swap', }, { id: '2475', contest_id: 'JAGWinter2008-day3', - problem_index: '2475', + problem_index: 'H', name: 'Round Table', - title: '2475. Round Table', + title: 'Round Table', }, { id: '2476', contest_id: 'JAGWinter2008-day3', - problem_index: '2476', + problem_index: 'I', name: 'Strange Currency System', - title: '2476. Strange Currency System', + title: 'Strange Currency System', }, { id: '2477', contest_id: 'JAGWinter2008-day3', - problem_index: '2477', + problem_index: 'J', name: 'Tangram', - title: '2477. Tangram', + title: 'Tangram', }, { id: '2478', contest_id: 'JAGWinter2008-day3', - problem_index: '2478', + problem_index: 'K', name: 'Trading Ship', - title: '2478. Trading Ship', + title: 'Trading Ship', }, { id: '2479', contest_id: 'JAGWinter2009', - problem_index: '2479', + problem_index: 'A', name: 'Approximate Circle', - title: '2479. Approximate Circle', + title: 'Approximate Circle', }, { id: '2480', contest_id: 'JAGWinter2009', - problem_index: '2480', + problem_index: 'B', name: 'Blame Game', - title: '2480. Blame Game', + title: 'Blame Game', }, { id: '2481', contest_id: 'JAGWinter2009', - problem_index: '2481', + problem_index: 'C', name: 'Earn Big', - title: '2481. Earn Big', + title: 'Earn Big', }, { id: '2482', contest_id: 'JAGWinter2009', - problem_index: '2482', + problem_index: 'D', name: 'Exportation in Space', - title: '2482. Exportation in Space', + title: 'Exportation in Space', }, { id: '2483', contest_id: 'JAGWinter2009', - problem_index: '2483', + problem_index: 'E', name: 'Laser Puzzle', - title: '2483. Laser Puzzle', + title: 'Laser Puzzle', }, { id: '2484', contest_id: 'JAGWinter2009', - problem_index: '2484', + problem_index: 'F', name: 'Magnum Tornado', - title: '2484. Magnum Tornado', + title: 'Magnum Tornado', }, { id: '2485', contest_id: 'JAGWinter2009', - problem_index: '2485', + problem_index: 'G', name: "Nezumi's Treasure", - title: "2485. Nezumi's Treasure", + title: "Nezumi's Treasure", }, { id: '2486', contest_id: 'JAGWinter2009', - problem_index: '2486', + problem_index: 'H', name: 'Testing Sorting Networks', - title: '2486. Testing Sorting Networks', + title: 'Testing Sorting Networks', }, { id: '2487', contest_id: 'JAGWinter2009', - problem_index: '2487', + problem_index: 'I', name: 'Train King', - title: '2487. Train King', + title: 'Train King', }, { id: '2488', contest_id: 'JAGWinter2009', - problem_index: '2488', + problem_index: 'J', name: 'Tree Construction', - title: '2488. Tree Construction', + title: 'Tree Construction', }, { id: '2369', contest_id: 'JAGWinter2010-day3', - problem_index: '2369', + problem_index: 'A', name: 'CatChecker', - title: '2369. CatChecker', + title: 'CatChecker', }, { id: '2370', contest_id: 'JAGWinter2010-day3', - problem_index: '2370', + problem_index: 'B', name: 'RabbitWalking', - title: '2370. RabbitWalking', + title: 'RabbitWalking', }, { id: '2371', contest_id: 'JAGWinter2010-day3', - problem_index: '2371', + problem_index: 'C', name: 'TransferTrain', - title: '2371. TransferTrain', + title: 'TransferTrain', }, { id: '2372', contest_id: 'JAGWinter2010-day3', - problem_index: '2372', + problem_index: 'D', name: 'IkaNumber', - title: '2372. IkaNumber', + title: 'IkaNumber', }, { id: '2373', contest_id: 'JAGWinter2010-day3', - problem_index: '2373', + problem_index: 'E', name: 'HullMarathon', - title: '2373. HullMarathon', + title: 'HullMarathon', }, { id: '2374', contest_id: 'JAGWinter2010-day3', - problem_index: '2374', + problem_index: 'F', name: 'RabbitLunch', - title: '2374. RabbitLunch', + title: 'RabbitLunch', }, { id: '2375', contest_id: 'JAGWinter2010-day3', - problem_index: '2375', + problem_index: 'G', name: 'CarrotBreeding', - title: '2375. CarrotBreeding', + title: 'CarrotBreeding', }, { id: '2376', contest_id: 'JAGWinter2010-day3', - problem_index: '2376', + problem_index: 'H', name: 'DisconnectedGame', - title: '2376. DisconnectedGame', + title: 'DisconnectedGame', }, { id: '2377', contest_id: 'JAGWinter2010-day3', - problem_index: '2377', + problem_index: 'I', name: 'ThreeRooks', - title: '2377. ThreeRooks', + title: 'ThreeRooks', }, { id: '2378', contest_id: 'JAGWinter2010-day3', - problem_index: '2378', + problem_index: 'J', name: 'SolveMe', - title: '2378. SolveMe', + title: 'SolveMe', }, { id: '2379', contest_id: 'JAGWinter2010-day4', - problem_index: '2379', + problem_index: 'A', name: 'Bicube', - title: '2379. Bicube', + title: 'Bicube', }, { id: '2380', contest_id: 'JAGWinter2010-day4', - problem_index: '2380', + problem_index: 'B', name: 'Bubble Puzzle', - title: '2380. Bubble Puzzle', + title: 'Bubble Puzzle', }, { id: '2381', contest_id: 'JAGWinter2010-day4', - problem_index: '2381', + problem_index: 'C', name: 'Fair Game', - title: '2381. Fair Game', + title: 'Fair Game', }, { id: '2382', contest_id: 'JAGWinter2010-day4', - problem_index: '2382', + problem_index: 'D', name: 'King Slime', - title: '2382. King Slime', + title: 'King Slime', }, { id: '2383', contest_id: 'JAGWinter2010-day4', - problem_index: '2383', + problem_index: 'E', name: 'Rabbit Game Playing', - title: '2383. Rabbit Game Playing', + title: 'Rabbit Game Playing', }, { id: '2384', contest_id: 'JAGWinter2010-day4', - problem_index: '2384', + problem_index: 'F', name: 'Rabbit Jumping', - title: '2384. Rabbit Jumping', + title: 'Rabbit Jumping', }, { id: '2385', contest_id: 'JAGWinter2010-day4', - problem_index: '2385', + problem_index: 'G', name: 'Shelter', - title: '2385. Shelter', + title: 'Shelter', }, { id: '2386', contest_id: 'JAGWinter2010-day4', - problem_index: '2386', + problem_index: 'H', name: 'Sightseeing Tour', - title: '2386. Sightseeing Tour', + title: 'Sightseeing Tour', }, { id: '2387', contest_id: 'JAGWinter2010-day4', - problem_index: '2387', + problem_index: 'I', name: 'Tampopo Machine', - title: '2387. Tampopo Machine', + title: 'Tampopo Machine', }, { id: '2388', contest_id: 'JAGWinter2010-day4', - problem_index: '2388', + problem_index: 'J', name: 'Top of the Hill', - title: '2388. Top of the Hill', + title: 'Top of the Hill', }, { id: '2340', contest_id: 'JAGWinter2011', - problem_index: '2340', + problem_index: 'A', name: "Carpenters' Language", - title: "2340. Carpenters' Language", + title: "Carpenters' Language", }, { id: '2341', contest_id: 'JAGWinter2011', - problem_index: '2341', + problem_index: 'B', name: 'Kth Sentence', - title: '2341. Kth Sentence', + title: 'Kth Sentence', }, { id: '2342', contest_id: 'JAGWinter2011', - problem_index: '2342', + problem_index: 'C', name: 'Light Road', - title: '2342. Light Road', + title: 'Light Road', }, { id: '2343', contest_id: 'JAGWinter2011', - problem_index: '2343', + problem_index: 'D', name: 'Matrix Operation', - title: '2343. Matrix Operation', + title: 'Matrix Operation', }, { id: '2344', contest_id: 'JAGWinter2011', - problem_index: '2344', + problem_index: 'E', name: 'Multi Ending Story', - title: '2344. Multi Ending Story', + title: 'Multi Ending Story', }, { id: '2345', contest_id: 'JAGWinter2011', - problem_index: '2345', + problem_index: 'F', name: 'Network Reliability', - title: '2345. Network Reliability', + title: 'Network Reliability', }, { id: '2346', contest_id: 'JAGWinter2011', - problem_index: '2346', + problem_index: 'G', name: 'Runaway Domino', - title: '2346. Runaway Domino', + title: 'Runaway Domino', }, { id: '2347', contest_id: 'JAGWinter2011', - problem_index: '2347', + problem_index: 'H', name: 'Sunny Graph', - title: '2347. Sunny Graph', + title: 'Sunny Graph', }, { id: '2348', contest_id: 'JAGWinter2011', - problem_index: '2348', + problem_index: 'I', name: 'Testing Circuits', - title: '2348. Testing Circuits', + title: 'Testing Circuits', }, { id: '2349', contest_id: 'JAGWinter2011', - problem_index: '2349', + problem_index: 'J', name: 'World Trip', - title: '2349. World Trip', + title: 'World Trip', }, { id: '1088', diff --git a/src/lib/clients/aizu_online_judge/clients.test.ts b/src/lib/clients/aizu_online_judge/clients.test.ts index 3ca1e6830..6a17d8f0b 100644 --- a/src/lib/clients/aizu_online_judge/clients.test.ts +++ b/src/lib/clients/aizu_online_judge/clients.test.ts @@ -321,16 +321,22 @@ describe('AojChallengesApiClient', () => { expect(tasks.length).toBe(expectedCount); }); - test('keeps the problem id as problem_index for JAG REGIONAL tasks', async () => { + test('assigns problem letters per contest in ascending problem id order', async () => { const tasks = await client.getTasks({ contestType: 'JAG', round: 'REGIONAL' }); - expect(getProblemIndicesByTaskId(tasks, 'JAGRegional2005')).toEqual({ - '2024': '2024', - '2025': '2025', - '2026': '2026', - '2027': '2027', - '2028': '2028', - '2029': '2029', + expect(getProblemIndicesByTaskId(tasks, 'JAGRegional2022')).toEqual({ + '3346': 'A', + '3347': 'B', + '3348': 'C', + '3349': 'D', + '3350': 'E', + '3351': 'F', + '3352': 'G', + '3353': 'H', + '3354': 'I', + '3355': 'J', + '3356': 'K', + '3357': 'L', }); }); }); diff --git a/src/lib/clients/aizu_online_judge/clients.ts b/src/lib/clients/aizu_online_judge/clients.ts index 0ab8ba119..389ce00f8 100644 --- a/src/lib/clients/aizu_online_judge/clients.ts +++ b/src/lib/clients/aizu_online_judge/clients.ts @@ -117,9 +117,9 @@ export class AojChallengesApiClient { }); } - // Scope of the problem-letter migration; JAG Regional and PCK keep the problem id for now. - private usesProblemLetters({ contestType, round }: ChallengeParams): boolean { - return contestType === 'ICPC' || (contestType === 'JAG' && round === 'PRELIM'); + // Scope of the problem-letter migration; PCK keeps the problem id for now. + private usesProblemLetters({ contestType }: ChallengeParams): boolean { + return contestType === 'ICPC' || contestType === 'JAG'; } private getCacheKey({ contestType, round }: ChallengeParams): string { diff --git a/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts b/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts index 13dab6bd0..bec4a9cb6 100644 --- a/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts +++ b/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts @@ -7,16 +7,68 @@ import { tasks } from '../../../../prisma/tasks'; import { assignProblemLetters } from './utils'; -// Same scope as the problem-letter migration; JAG training camps are AOJ_JAG but keep problem ids. +// Same scope as the problem-letter migrations: every AOJ ICPC and JAG contest. function usesProblemLetters(contestId: string): boolean { const contestType = classifyContest(contestId); - return ( - contestType === ContestType.AOJ_ICPC || - (contestType === ContestType.AOJ_JAG && contestId.startsWith('JAGPrelim')) - ); + return contestType === ContestType.AOJ_ICPC || contestType === ContestType.AOJ_JAG; } +// Problems missing from AOJ shift the basic rule here, so letters follow the maintainer's problem list. +const LETTERS_OF_SHIFTED_CONTESTS: Record> = { + 'JAGSummer2006-day1': { '2057': 'B' }, + 'JAGSummer2006-day3': { + '2067': 'B', + '2068': 'C', + '2069': 'D', + '2070': 'E', + '2071': 'F', + '2072': 'G', + }, + 'JAGWinter2006-day2': { '2073': 'D', '2074': 'F' }, + 'JAGWinter2006-day3': { '2075': 'A', '2076': 'D', '2077': 'G', '2078': 'H' }, + 'JAGWinter2007-day3': { + '2122': 'A', + '2123': 'B', + '2124': 'C', + '2125': 'D', + '2126': 'F', + '2127': 'G', + '2128': 'H', + '2129': 'I', + '2130': 'J', + }, + 'JAGSummer2024-day3': { + '3637': 'A', + '3638': 'B', + '3639': 'C', + '3640': 'D', + '3641': 'E', + '3642': 'F', + '3644': 'H', + '3645': 'I', + '3646': 'J', + '3647': 'K', + }, + JAGRegional2005: { + '2024': 'A', + '2025': 'B', + '2026': 'D', + '2027': 'E', + '2028': 'G', + '2029': 'I', + }, + JAGRegional2013: { + '2568': 'D', + '2569': 'E', + '2570': 'F', + '2571': 'G', + '2572': 'H', + '2573': 'I', + '2574': 'J', + }, +}; + function groupProblemIndicesByContest(): Map> { const indicesByContest = new Map>(); @@ -33,28 +85,44 @@ function groupProblemIndicesByContest(): Map> { return indicesByContest; } -describe('problem_index of ICPC / JAG Prelim tasks in prisma/tasks.ts', () => { +describe('problem_index of AOJ ICPC / JAG tasks in prisma/tasks.ts', () => { const indicesByContest = groupProblemIndicesByContest(); // Guards against the filter silently matching fewer tasks. - test('covers 79 contests and 632 tasks', () => { + test('covers 145 contests and 1257 tasks', () => { const taskCount = [...indicesByContest.values()].reduce( (sum, indices) => sum + indices.size, 0, ); - expect(indicesByContest.size).toBe(79); - expect(taskCount).toBe(632); + expect(indicesByContest.size).toBe(145); + expect(taskCount).toBe(1257); + }); + + test.each( + [...indicesByContest].filter(([contestId]) => !(contestId in LETTERS_OF_SHIFTED_CONTESTS)), + )('%s follows the basic rule (ascending problem ids)', (_contestId, indices) => { + expect(indices).toEqual(assignProblemLetters([...indices.keys()])); }); - test.each([...indicesByContest])( - '%s matches the letters assigned on import', - (_contestId, indices) => { - expect(indices).toEqual(assignProblemLetters([...indices.keys()])); + test.each(Object.entries(LETTERS_OF_SHIFTED_CONTESTS))( + '%s follows the official letters', + (contestId, letters) => { + expect(Object.fromEntries(indicesByContest.get(contestId) ?? [])).toEqual(letters); }, ); test('ICPC Prelim 2024 F is problem 1677', () => { expect(indicesByContest.get('ICPCPrelim2024')?.get('1677')).toBe('F'); }); + + test('no title starts with its problem id', () => { + const prefixedTaskIds = tasks + .filter( + (task) => usesProblemLetters(task.contest_id) && task.title.startsWith(`${task.id}. `), + ) + .map((task) => task.id); + + expect(prefixedTaskIds).toEqual([]); + }); }); From c835754b7e22cfc927ba9b4890543120c9294e07 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 00:17:25 +0000 Subject: [PATCH 5/8] fix: rank every contest row in the JAG letter migration and check titles Address the Codex cross-review: - Migration step (1) ranked only rows still holding the problem id, so a contest mixing letter rows and id rows would restart at A. Rank every row of the contest and restrict only the UPDATE to id rows. A read-only simulation (2967 already "A") now yields B, C, D for 2968-2970 instead of A, B, C. The local DB was restored to its pre-migration snapshot, and the fixed migration re-applied: 625/625 rows match the preview. - The seed test only checked that no title starts with ". ", so an emptied or swapped title passed. Check that every AOJ ICPC / JAG title equals the problem name instead; both cases now fail. Refs #4030 Co-Authored-By: Claude Opus 5.5 --- .../migration.sql | 6 +++--- .../aizu_online_judge/seed_problem_letters.test.ts | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql b/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql index 63791c23b..792c7c213 100644 --- a/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql +++ b/prisma/migrations/20261025000719_update_aoj_jag_task_table_index/migration.sql @@ -19,8 +19,6 @@ BEGIN FROM "task" WHERE contest_type = 'AOJ_JAG' AND contest_id ~ '^JAG(Summer|Winter|Spring|Regional)' - -- Rows that already hold a letter (added later with letters) are left as they are. - AND task_table_index = task_id ) UPDATE "task" AS t SET "task_table_index" = letters.letter @@ -28,7 +26,9 @@ BEGIN WHERE t."task_id" = letters.task_id -- Redundant with the CTE filter, but keeps the blast radius fixed. AND t."contest_type" = 'AOJ_JAG' - AND t."contest_id" ~ '^JAG(Summer|Winter|Spring|Regional)'; + AND t."contest_id" ~ '^JAG(Summer|Winter|Spring|Regional)' + -- Rank every row above, but leave rows that already hold a letter (added with letters) as they are. + AND t."task_table_index" = t."task_id"; GET DIAGNOSTICS updated_count = ROW_COUNT; IF updated_count NOT IN (0, 467, 625) THEN diff --git a/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts b/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts index bec4a9cb6..ae85a8e96 100644 --- a/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts +++ b/src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts @@ -116,13 +116,12 @@ describe('problem_index of AOJ ICPC / JAG tasks in prisma/tasks.ts', () => { expect(indicesByContest.get('ICPCPrelim2024')?.get('1677')).toBe('F'); }); - test('no title starts with its problem id', () => { - const prefixedTaskIds = tasks - .filter( - (task) => usesProblemLetters(task.contest_id) && task.title.startsWith(`${task.id}. `), - ) + // AOJ titles are the plain problem name: no ". " prefix and nothing lost when it was stripped. + test('every title equals the problem name', () => { + const mismatchedTaskIds = tasks + .filter((task) => usesProblemLetters(task.contest_id) && task.title !== task.name) .map((task) => task.id); - expect(prefixedTaskIds).toEqual([]); + expect(mismatchedTaskIds).toEqual([]); }); }); From 597104fd3211fab0e0fe43b6b8554551fd20b239 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 00:17:33 +0000 Subject: [PATCH 6/8] chore: remove the one-off JAG problem letter cross-check script It produced the letters written into prisma/tasks.ts and the migration's overrides. Recover it with `git show c605d3bd:docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts` if the letters need to be checked again. Co-Authored-By: Claude Opus 5.5 --- .../generate_aoj_jag_problem_letters.ts | 977 ------------------ 1 file changed, 977 deletions(-) delete mode 100644 docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts b/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts deleted file mode 100644 index b900de2c1..000000000 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts +++ /dev/null @@ -1,977 +0,0 @@ -/** - * Throwaway script for PR2 (see plan-pr2.md). Committed once for traceability, then deleted. - * - * Builds the AOJ problem id -> problem letter table for the JAG training camps and JAG Regional - * from prisma/tasks.ts. Letters come from the position in the maintainer's problem list (a - * spreadsheet) transcribed in OFFICIAL_CONTESTS, skipping problems absent from AOJ's contest - * listing. Prints the table as JSON only when every title matches. - * - * Usage: - * pnpm exec tsx docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts - */ -import { tasks } from '../../../../prisma/tasks'; - -const TARGET_CONTEST_ID = /^JAG(Summer|Winter|Spring|Regional)\d{4}(-day\d+[A-Z]?)?$/; -const EXPECTED_CONTEST_COUNT = 66; -const EXPECTED_TASK_COUNT = 625; -const TRUNCATION_MARK = '…'; - -interface OfficialContest { - /** Official titles in letter order. A trailing "…" marks a title cut off in the spreadsheet. */ - titles: string[]; - /** Letters of official problems absent from prisma/tasks.ts (not in AOJ's contest listing). */ - absentLetters: string[]; -} - -type Problem = { id: string; name: string }; - -function official(titles: string[], absentLetters: string[] = []): OfficialContest { - return { titles, absentLetters }; -} - -// Transcribed from the maintainer's spreadsheet (screenshots of 2026-10-24, rows 2-1025). -const OFFICIAL_CONTESTS: Record = { - 'JAGSummer2024-day2': official([ - 'Add Add Add', - 'Broken Parentheses', - 'Convenient Banknotes', - 'Do Make Segment Tree', - 'Expression Sum', - 'Flip Path on Rooted Tree', - 'Give Me a Lot of Triangles', - 'Half Plane Painting', - 'I Love Square Number', - 'Just Believe in Binary Search', - 'K-th Nondivisor', - 'Linear Time Inversion Number', - 'Max Sum of GCD', - 'Noncoprime Subsequences', - ]), - 'JAGSummer2024-day3': official( - [ - 'Tower Defense', - 'Renovation', - 'Commutativity', - 'Paper Cut Game', - 'Ball Passing', - 'Halfway Through the Book', - 'Interactive String Guessing', - 'Coins on a Tree', - 'Fragile Tree', - 'Draw the Tree', - 'Equal or Not Equal', - ], - ['G'], - ), - 'JAGSummer2023-day2': official([ - 'Sum of Product of Binomial Coefficients', - 'Mercurialist', - 'Umbrella Queries', - 'Gemini Tree (Ver.Jadeite)', - 'Gemini Tree (Ver.Lapislazuli)', - 'Tea time in the grand garden', - 'Fraises dans une boite', - 'Empty Quartz', - 'Distance Permutation', - 'Knight Game', - 'Drifting', - 'Disjoint-Sparse-Table Optimization', - ]), - 'JAGSummer2023-day3': official([ - 'Roller Coaster', - 'Break a Prison', - 'Camp room assignment', - 'Many-hued Tree', - 'Gacha 101', - 'Digit-only subrectangles', - 'Convex Polygon MST', - 'LCP Queries', - 'Best parentheses', - 'Edit distance on table', - 'Odd trip plans', - ]), - JAGRegional2022: official([ - 'Stop and Go', - '1-Player Concentration', - 'Track Train Trail', - '2-LIS', - 'Sharp 2-SAT', - 'Seimei Handan 999.0', - 'Avoid bombings', - 'Sloppy city planning', - 'N-1 Truths and a Lie', - 'Most distant point from the stations', - 'Sort Compressed Strings', - 'Tree Automorphism', - ]), - JAGRegional2021: official([ - 'Harvest', - 'Parse the Syntax Tree', - 'Permutation Magic', - 'MFP: Most Fluctuated Player', - "Underground's SUNDAY", - 'qarentheziz zepuence', - 'Pizza delivery', - 'include', - '(N+1)-legged race', - 'Isomorphic?', - 'Zombie Land 2', - ]), - JAGRegional2020: official([ - 'On-Call', - 'Warp Points', - 'Rebound Sequences', - 'Surround the Castle', - 'Buttons', - 'Mountain View', - 'Yet Another Expression Mining', - 'Aggressive Traveller', - 'Irreversible Reactions', - 'X-percent Blooming', - 'Feed candies', - ]), - 'JAGSummer2019-day1': official([ - 'AIGo', - 'Non-trivial Common Divisor', - 'Universal and Existential Quantifiers', - 'Permutation Sort', - 'Consistent Trading', - 'All your base are belong to us', - 'Route Calculator Returns', - 'N-by-M grid calculation', - 'Zombie Land', - 'Rooks Game', - 'Bombing', - ]), - 'JAGSummer2019-day2': official([ - 'Undo Swapping', - 'Spent Fuel Disposal', - 'Cyclic String', - 'Nose And Lung', - 'Expensive Function', - 'Clique Drawing', - 'Human Seperation', - 'Permutation Score', - 'Palindrome Compliment', - 'Prefix Suffix Nim', - ]), - 'JAGSummer2019-day3': official([ - 'Union Ball', - 'AddMulSubDiv', - 'Shuttle Run', - 'XORANDORBAN', - 'Red Black Balloons', - 'Invariant Tree', - 'Toss Cut Tree', - 'Colorful Tree', - 'Add', - 'Horizontal-Vertical Permutation', - ]), - 'JAGSummer2018-day3': official([ - 'GuruGuru', - 'Colorful Drink', - "Santa's Gift", - 'Prefix Suffix Search', - 'Magic Triangles', - 'Nim without Zero', - 'Additions', - 'Enlarge Circles', - 'Sum of QQ', - 'Prime Routing', - 'Rough Sorting', - ]), - JAGRegional2017: official([ - 'Window', - 'Tournament Chart', - 'Prime-Factor Prime', - 'Revenge of the Broken Door', - 'Tree Separator', - 'RPG Maker', - 'Coin Slider', - 'Separate String', - 'Revenge of the Endless BFS', - 'Farm Village', - 'Conveyor Belt', - ]), - 'JAGSummer2017-day3': official([ - 'Star in Parentheses', - 'Slimming Plan', - 'Ninja Map', - 'Janken Master', - 'Route Calculator', - 'Endless BFS', - 'Low Range-Sum Matrix', - 'Tiny Room', - "Librarian's Work", - 'Sum Source Detection', - 'Permutation Period', - ]), - JAGRegional2016: official([ - 'Best Matched Pair', - 'Help the Princess!', - "We don't wanna work!", - 'Parentheses', - 'Similarity of Subtrees', - 'Escape from the Hell', - 'Share the Ruins Preservation', - 'Pipe Fitter and the Fierce Dogs', - 'Multisect', - 'Compressed Formula', - 'Non-redundant Drive', - ]), - JAGRegional2015: official([ - 'M and A', - 'Change a Password', - 'Delete Files', - 'Line Gimmick', - 'Shifting a Matrix', - 'Modern Announce Network', - 'Cube Dividing', - 'Donut Decoration', - 'Shortest Bridge', - 'Longest Shortest Path', - 'Optimal Tournament', - ]), - 'JAGSummer2015-day2': official([ - "Let's Solve Geometric Problems", - 'Jail', - 'ABC Gene', - 'Dark Room', - 'An Equation in a Mine', - 'Nearly Cyclic String', - 'Escape', - 'Bit Operation Game', - 'Twin Reverse', - 'Connection', - 'Leapfrog', - ]), - 'JAGSummer2015-day4': official([ - 'Where is the Boundary', - 'Vector Field', - 'Kuru Kuru Sushi', - 'Identity Function', - 'Enclose Points', - 'Marching Course', - 'Surface Area of Cubes', - 'Laser Cutter', - 'Live Programming', - 'Black Company', - ]), - JAGSpring2015: official([ - 'Balanced Paths', - 'Card Game Strategy', - 'Casino', - 'Content Delivery', - 'Cost Performance Flow', - 'ICPC Teams', - 'JAG-channel II', - 'Kimagure Cleaner', - 'Midpoint', - 'New Game AI', - 'Runner and Sniper', - 'Wall Making Game', - ]), - 'JAGSummer2014-day2': official([ - 'Manhattan', - 'Dictionary', - 'Clique Coloring', - 'Dense Amidakuji', - 'Cellular Automaton', - 'Directions', - 'Snake', - 'Distance Sum', - 'Substring Pairs', - 'Hyperrectangle', - ]), - JAGRegional2014: official([ - 'North North West', - 'Unknown Switches', - 'Speedrun', - 'Flowers', - 'Square in Circles', - 'Reverse a Road II', - 'Cookie Counter', - 'Points and Lines', - 'Color the Map Extreme', - 'Website Tour', - 'Idempotent Filter', - ]), - 'JAGSummer2014-day4': official([ - 'Yamanote Line', - 'Prowler', - 'Magic Bullet', - 'Dinner', - 'AI', - 'Longest Match', - 'Recycle', - 'Tournament', - 'The Capital', - 'Vongress', - ]), - JAGSpring2014: official([ - 'Breadth-First Search by Foxpower', - 'Cube Coloring', - 'Decoding Ancient Messages', - 'LR', - 'Parentheses', - 'Polygon Guards', - 'Proportional Representation', - 'RLE Replacement', - 'Tokyo Olympics Center', - 'Unfair Game', - ]), - JAGRegional2013: official( - [ - 'Broken Audio Signal', - 'Restore Calculation', - 'SIRO Challenge', - 'Everlasting -One-', - 'Putter', - 'Shipura', - 'Floating Islands', - 'Venn Diagram', - 'Overwriting Game', - 'Magical Switches', - ], - ['A', 'B', 'C'], - ), - 'JAGSummer2013-day2': official([ - 'Koto Distance', - 'Evacuation Route', - 'Apples', - 'TiMe Table', - 'Pattern Language', - 'Social Monsters', - 'Perm Query', - ]), - 'JAGSummer2013-day3': official([ - 'Invest Master', - 'Minus One', - 'Wave Attack', - 'Fast Division', - 'Ordering', - 'Phutball', - 'Unordered Operators', - 'Almost Same Substring', - 'A + B', - 'KuruKuruKururin', - 'Air Pollution', - 'Trip to Kyoto', - ]), - 'JAGSummer2013-day4': official([ - 'Thread Tree', - 'Trodden Cable', - 'Fox Observation', - 'Removing Magical Tiles', - 'Optimal alpha beta pruning', - 'Graph Automata Player', - 'Spotlight Movement', - 'Gravity Point', - 'Multi Path Story', - 'Rotation Game', - ]), - JAGSpring2013: official([ - 'Everlasting Zero', - 'Integer in Integer', - 'Iyasugigappa', - 'Medical Inspection', - 'Minimum Spanning Tree', - 'Point Distance', - 'Revenge of Minimum Cost Flow', - 'Rings', - 'The J-th Number', - 'Tree Reconstruction', - ]), - JAGRegional2012: official([ - 'Dictionary', - "Texas hold 'em", - 'Median Tree', - 'Billiard', - 'Stack Maze', - "Counting 1's", - 'Ancient Commemorative Monolith', - 'Magical Bridges', - 'Hashigo Sama', - 'Ancient Scrolls', - ]), - 'JAGSummer2012-day2': official([ - 'Kakezan', - 'A Holiday of Miss Brute Force', - 'Treasure Hunt', - 'hosonagaitokoro', - 'Lost Number', - 'marukaite', - 'Longest Increasing Sequence', - 'House Moving', - 'Sports Days 2.0', - 'Final Defense Line', - ]), - 'JAGSummer2012-day3A': official([ - 'Audition', - 'Zero Division Checker', - 'Card', - 'DNA', - 'YAML', - 'Hakone', - ]), - 'JAGSummer2012-day3B': official([ - 'Kagisys', - 'FizzBuzz', - 'ConvexCut', - 'ReverseSort', - 'Substring', - 'MinimumCostPath', - 'Enumeration', - ]), - 'JAGSummer2012-day4': official([ - 'A Two Floors Dungeon', - 'Area Folding', - 'Connect', - 'Do use segment tree', - 'Move on Dice', - 'Pipeline Plans', - 'Presentation', - 'Repairing', - 'Sun and Moon', - 'Usoperanto', - ]), - JAGSpring2012: official([ - 'Asynchronous Exceptions', - 'AYBABTU', - 'Billiards Sorting', - 'Digit', - 'Dungeon Creation', - 'Longest Lane', - 'Play in Basic', - 'Skyland', - 'Three-way Branch', - 'Tree Allocation', - ]), - JAGWinter2011: official([ - "Carpenters' Language", - 'Kth Sentence', - 'Light Road', - 'Matrix Operation', - 'Multi Ending Story', - 'Network Reliability', - 'Runaway Domino', - 'Sunny Graph', - 'Testing Circuits', - 'World Trip', - ]), - JAGRegional2011: official([ - 'Infinity Maze', - 'Butterfly', - 'Chinese Classics', - 'Revenge of Champernowne Constant', - 'Full Text Search', - 'Mysterious Maze', - 'Number Sorting', - 'Sky Jump', - 'Mobile Network', - 'Blue Forest', - ]), - 'JAGSummer2011-day2': official([ - 'Attack the Moles', - 'Brilliant Stars', - 'Common Palindromes', - 'Dangerous Tower', - 'Entangled with Lottery', - 'Power of Power', - 'Quest of Merchant', - 'Rectangular Stamps', - 'Starting Line', - 'Tiles are Colorful', - ]), - 'JAGSummer2011-day3': official([ - 'Calender Colors', - 'Sleeping Time', - 'On or Off', - 'Marathon Match', - 'Reverse Roads', - 'Beautiful Currency', - 'Rabbit Party', - 'Palindrome Generator', - 'White Bird', - 'Vector Compression', - ]), - 'JAGSummer2011-day4': official([ - 'Rose Garden Witch', - 'Dessert Witch', - 'Magical Girl Sayaka-chan', - 'Box Witch', - 'Scribbling witch', - 'Shadow Witch', - 'Mermaid Witch', - 'Class Representative Witch', - 'Set-constructing Witch', - 'Soul Gem Game', - ]), - 'JAGWinter2010-day3': official([ - 'CatChecker', - 'RabbitWalking', - 'TransferTrain', - 'IkaNumber', - 'HullMarathon', - 'RabbitLunch', - 'CarrotBreeding', - 'DisconnectedGame', - 'ThreeRooks', - 'SolveMe', - ]), - 'JAGWinter2010-day4': official([ - 'Bicube', - 'Bubble Puzzle', - 'Fair Game', - 'King Slime', - 'Rabbit Game Playing', - 'Rabbit Jumping', - 'Shelter', - 'Sightseeing Tour', - 'Tampopo Machine', - 'Top of the Hill', - ]), - JAGRegional2010: official([ - 'Era Name', - 'Step Step Evolution', - 'Dungeon Quest II', - 'Dice Room', - 'Alice and Bomb', - 'Two-Wheel Buggy', - 'Camera Control', - 'Road Construction', - 'Operator', - 'Merry Christmas', - ]), - 'JAGSummer2010-day2': official([ - 'Earth Invasion Diary of Miyabi-sensei', - 'A Way to Invite Friends', - 'Space-Time Sugoroku Road', - 'My friends are small', - 'Roads on Towns', - '10-Year-Old Dynamic Programming', - 'Spring Tiles', - 'Remodeling Plan for Neko-Nabe (tentative)', - 'Intelligible Double Magic', - "Person responsible for problem description don't w…", - ]), - 'JAGSummer2010-day3': official([ - 'Ennichi', - 'Carrot Tour', - 'Usagitobi', - 'Graph Construction', - 'Rabbit Plays Games!', - 'The Castle', - 'Nurie', - 'Nearest Station', - 'Lapin Noir', - 'Usaneko Matrix', - ]), - 'JAGSummer2010-day4': official([ - "Alien's Counting", - 'Kaeru Jump', - 'Save your cats', - 'Dungeon Wall', - 'Psychic Accelerator', - 'Bouldering', - "DON'T PANIC!", - 'Ropeway', - 'How to Create a Good Game', - 'Cruel Bingo', - ]), - JAGWinter2009: official([ - 'Approximate Circle', - 'Blame Game', - 'Earn Big', - 'Exportation in Space', - 'Laser Puzzle', - 'Magnum Tornado', - "Nezumi's Treasure", - 'Testing Sorting Networks', - 'Train King', - 'Tree Construction', - ]), - JAGRegional2009: official([ - 'Whist', - 'For the Peace', - 'Champernowne Constant', - 'Futon', - 'Safe Area', - 'Water Tank', - "Neko's Treasure", - 'Eleven Lover', - 'Crystal Jails', - 'Cave Explorer', - ]), - 'JAGSummer2009-day2': official([ - 'Infected Computer', - 'Magic Slayer', - 'Dial Lock', - 'Double Sorting', - 'Symmetry', - 'Voronoi Island', - 'Defend the Bases', - 'Galaxy Wide Web Service', - 'Tatami', - 'Revenge of the Round Table', - ]), - 'JAGSummer2009-day3': official([ - 'Strange String Manipulation', - 'Erratic Sleep Habits', - 'Find the Point', - "Luigi's Tavern", - 'Colored Octahedra', - 'Marked Ancestor', - 'Strange Couple', - "Queen's Case", - 'Wind Passages', - 'Secret Operation', - ]), - 'JAGWinter2008-day2': official([ - 'Adhoc Translation', - 'Artistic Art Museum', - 'Complex Integer Solutions', - 'Dial Key', - 'Dungeon Master', - 'Exciting Bicycle', - 'Fuel Problem', - 'Magic Walls', - 'Sort by Hand', - 'Substring Expression', - 'Up Above the World So High', - ]), - 'JAGWinter2008-day3': official([ - 'Cache Control', - 'Cover Time', - 'Craftsman', - 'Divide the Water', - "Mickle's Beam", - 'Poor Computer', - 'Riffle Swap', - 'Round Table', - 'Strange Currency System', - 'Tangram', - 'Trading Ship', - ]), - JAGRegional2008: official( - [ - 'Everlasting...?', - 'Headstrong Student', - 'Dig or Climb', - 'Rotation Estimation', - 'Optimal Rest', - 'Controlled Tournament', - 'Entangled Tree', - 'Ramen Shop', - "Cousin's Aunt", - 'Colony Maintenance', - ], - ['J'], - ), - 'JAGSummer2008-day2': official([ - 'Pi is Three', - 'Left Hand Rule', - 'Alice and Bob', - 'Deadly Dice Game', - 'Reverse a Road', - 'Webby Subway', - 'Time Trial', - 'Vending Machine', - 'Memory Match', - 'Tile Puzzle', - ]), - 'JAGSummer2008-day4': official([ - "Girls' Party", - 'Bitwise Kingdom', - 'Adaptive Time Slicing Quantization', - 'Reaction', - 'Magical Island', - 'Ninja Legend', - 'Do It', - 'Robot Communication', - ]), - 'JAGWinter2007-day2': official([ - 'Defend the Nation', - 'Electrophoretic', - 'Median Filter', - 'Life Game', - 'Subdividing a Land', - 'Connect Line Segments', - 'Oil Company', - 'Finding the Top RPS Player', - 'Revenge of Voronoi', - 'Castle Wall', - ]), - 'JAGWinter2007-day3': official( - [ - 'Optimization of Combinatorial Circuits', - 'Divisor Function', - 'Magical Dungeon', - 'Land Mark', - 'Networking Company', - 'Alien Pianist', - 'Japanese Style Pub', - 'Two Rings', - 'Text Justification', - 'Billion Million Thousand', - ], - ['E'], - ), - JAGRegional2007: official([ - 'Space Coconut Crab II', - 'Sort the Panels', - 'Disarmament of the Units', - 'So Sleepy', - 'Alice in Foxland', - 'Lying about Your Age', - 'Turn Polygons', - "Robots' Crash", - 'Linear Ether Geometry', - ]), - 'JAGSummer2007-day1': official([ - 'Dance Dance Revolution', - 'Compress Files', - 'Save the Energy', - 'Goofy Converter', - 'Black Force', - ]), - 'JAGSummer2007-day2': official([ - 'Hit and Blow', - 'Turn Left', - '!', - 'Speed', - 'Spirograph', - 'Mysterious Dungeons', - 'Repeated Subsequences', - 'Petoris', - 'Pythagoraslope', - ]), - 'JAGSummer2007-day3': official([ - 'Election', - 'Jaggie Spheres', - 'Nagashi Soumen', - 'Private Teacher', - 'Triangles', - 'Two-finger Programming', - 'Walk under a Scorching Sun', - ]), - 'JAGWinter2006-day2': official( - [ - 'Cells', - 'Japan', - 'Maximum', - 'The Phantom', - 'Payment System', - 'Rakunarok', - 'Roof', - 'Seminar Room', - 'String Compression', - ], - ['A', 'B', 'C', 'E', 'G', 'H', 'I'], - ), - 'JAGWinter2006-day3': official( - [ - 'Dock to the Future', - 'Widget Factory', - 'Hills', - 'Flame of Nucleus', - 'Safe Packing', - 'The "Shortest" Pair of Paths', - 'Resource', - 'Exact Arithmetic', - 'Counting Triangles', - 'Wireing Assistant', - ], - ['B', 'C', 'E', 'F', 'I', 'J'], - ), - JAGRegional2006: official([ - 'Ruins', - 'Hyper Rock-Scissors-Paper', - 'Online Quiz System', - 'Rock Man', - 'Autocorrelation Function', - 'It Prefokery Pio', - 'Traffic', - 'Restriction Enzyme', - 'The Extreme Slalom', - ]), - 'JAGSummer2006-day1': official( - ['Monkey Business', 'The Closest Circle', 'Sort that Queue'], - ['A', 'C'], - ), - 'JAGSummer2006-day2': official([ - 'Moduic Squares', - 'Restaurant', - 'Tetrahedra', - 'International Party', - 'Hide-and-seek', - 'TV Watching', - 'Make Friendships', - 'Slippy Floors', - 'Roads in a City', - ]), - 'JAGSummer2006-day3': official( - [ - 'Rolling Cubes', - 'Reading Brackets in English', - 'Grated Radish', - 'Greedy, Greedy.', - 'First Experience', - 'Web 0.5', - "Philosopher's Stone", - ], - ['A'], - ), - JAGRegional2005: official( - [ - 'Blackjack', - 'Eight Princes', - 'Geometry?! Why Not??', - 'Divisor is the Conqueror', - 'Reading a Chord', - 'University Rankings', - 'Gather on the Clock', - 'Tiling Polygons', - 'Light The Room', - ], - ['C', 'F', 'H'], - ), -}; - -// Ignore case, spacing, and punctuation, which differ between the spreadsheet and AOJ. -function normalizeTitle(title: string): string { - return title.toLowerCase().replace(/[^a-z0-9]/g, ''); -} - -function matchesTitle(aojTitle: string, officialTitle: string): boolean { - const normalizedAojTitle = normalizeTitle(aojTitle); - const normalizedOfficialTitle = normalizeTitle(officialTitle); - - if (officialTitle.endsWith(TRUNCATION_MARK)) { - return normalizedAojTitle.startsWith(normalizedOfficialTitle); - } - - return normalizedAojTitle === normalizedOfficialTitle; -} - -function toLetter(position: number): string { - return String.fromCharCode(65 + position); -} - -function groupTargetProblems(): Map { - const problemsByContest = new Map(); - - for (const task of tasks) { - if (!TARGET_CONTEST_ID.test(task.contest_id)) { - continue; - } - - const problems = problemsByContest.get(task.contest_id) ?? []; - problems.push({ id: task.id, name: task.name }); - problemsByContest.set(task.contest_id, problems); - } - - for (const problems of problemsByContest.values()) { - problems.sort((first, second) => Number(first.id) - Number(second.id)); - } - - return problemsByContest; -} - -// Letters of the problems that AOJ lists, in order; absent ones are skipped, not shifted into. -function getPresentLetters({ titles, absentLetters }: OfficialContest): string[] { - return titles - .map((_title, position) => toLetter(position)) - .filter((letter) => !absentLetters.includes(letter)); -} - -function verifyContest(contestId: string, problems: Problem[]): string[] { - const officialContest = OFFICIAL_CONTESTS[contestId]; - - if (officialContest === undefined) { - return [`${contestId}: not in the official list`]; - } - - const presentLetters = getPresentLetters(officialContest); - const errors: string[] = []; - - if (problems.length !== presentLetters.length) { - errors.push( - `${contestId}: AOJ has ${problems.length} problems, expected ${presentLetters.length}`, - ); - } - - problems.forEach((problem, position) => { - const letter = presentLetters[position]; - const officialTitle = - letter === undefined ? undefined : officialContest.titles[letter.charCodeAt(0) - 65]; - - if (officialTitle === undefined || !matchesTitle(problem.name, officialTitle)) { - errors.push( - `${contestId} ${letter ?? '?'}: AOJ has "${problem.name}", expected "${officialTitle ?? '(none)'}"`, - ); - } - }); - - return errors; -} - -function main(): void { - const problemsByContest = groupTargetProblems(); - const taskCount = [...problemsByContest.values()].reduce( - (sum, problems) => sum + problems.length, - 0, - ); - - if (problemsByContest.size !== EXPECTED_CONTEST_COUNT || taskCount !== EXPECTED_TASK_COUNT) { - throw new Error( - `Extracted ${problemsByContest.size} contests / ${taskCount} tasks, expected ${EXPECTED_CONTEST_COUNT} / ${EXPECTED_TASK_COUNT}`, - ); - } - - const errors = [...problemsByContest].flatMap(([contestId, problems]) => - verifyContest(contestId, problems), - ); - const unknownContests = Object.keys(OFFICIAL_CONTESTS).filter( - (contestId) => !problemsByContest.has(contestId), - ); - - errors.push(...unknownContests.map((contestId) => `${contestId}: not in prisma/tasks.ts`)); - - console.error(`Contests: ${problemsByContest.size}, tasks: ${taskCount}`); - - if (errors.length > 0) { - errors.forEach((error) => console.error(`ERROR ${error}`)); - process.exitCode = 1; - - return; - } - - console.error(`All ${taskCount} titles match the official letters`); - - const letters: Record = {}; - const overrides: [string, string, string][] = []; - - for (const [contestId, problems] of problemsByContest) { - const presentLetters = getPresentLetters(OFFICIAL_CONTESTS[contestId]); - const shifted = problems.some( - (_problem, position) => presentLetters[position] !== toLetter(position), - ); - - problems.forEach((problem, position) => { - letters[problem.id] = presentLetters[position]; - - // Every row of a shifted contest is overridden, so the migration does not mix two rules. - if (shifted) { - overrides.push([contestId, problem.id, presentLetters[position]]); - } - }); - } - - console.error( - `Contests that differ from the basic rule: ${new Set(overrides.map(([contestId]) => contestId)).size} / ${overrides.length} rows`, - ); - console.log(JSON.stringify({ letters, overrides }, null, 2)); -} - -main(); From 2089fe9a34c9fd9208311c0fbb0073c0d9cc9d00 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 00:18:22 +0000 Subject: [PATCH 7/8] docs: record the PR2 implementation and the Codex review outcome Co-Authored-By: Claude Opus 5.5 --- .../plan-pr2.md | 35 +++++++++++++++---- .../task-title-index-change-impact/plan.md | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md index 3afd5c0e6..e8901aad0 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md @@ -4,7 +4,7 @@ AOJ の JAG 合宿系(夏 / 冬 / 春)と JAG 模擬地区の `task_table_in 全体の目的・共通の決定(M 番号)・データ移行の共通手順は [plan.md](./plan.md)、根拠データは [investigation.md](./investigation.md)。 -**状態**: 未着手。着手に必要なデータと環境(staging の件数)はそろっている。 +**状態**: 実装済み(ブランチ `#4030`、未マージ)。[実施記録](#実施記録2026-10-25) ## 範囲 @@ -97,13 +97,15 @@ BEGIN FROM "task" WHERE contest_type = 'AOJ_JAG' AND contest_id ~ '^JAG(Summer|Winter|Spring|Regional)' - -- Leaves rows that already have letters (PR2.1 data) untouched. - AND task_table_index = task_id ) UPDATE "task" AS t SET "task_table_index" = letters.letter FROM letters - WHERE t."task_id" = letters.task_id; + WHERE t."task_id" = letters.task_id + AND t."contest_type" = 'AOJ_JAG' + AND t."contest_id" ~ '^JAG(Summer|Winter|Spring|Regional)' + -- Rank every row above, but leave rows that already hold a letter (added with letters) as they are. + AND t."task_table_index" = t."task_id"; GET DIAGNOSTICS updated_count = ROW_COUNT; -- 0: empty DB (migrate reset), 467: seed without JAG Regional, 625: staging / production. @@ -145,7 +147,7 @@ END $$; - **範囲は `contest_id` の接頭辞で絞る**。`AOJ_JAG` には、PR1 で記号にした模擬国内も含まれる - **件数の想定値**: staging / 本番は 625 / 45 / 467(上の表から)。0 を含めるのは、`prisma migrate reset`(ローカル)が空の DB にマイグレーションを流してから seed を入れるため。開発者の既存の DB は合宿系だけを持つので 467 / 32 / 467 になる - **title の条件**: 「自分の `task_id` + `. `」で始まるものだけを対象にする。`~ '^[0-9]+\. '` だと、番号で始まる本来の title まで削りうる -- **`task_table_index = task_id` の条件**: PR2.1 の行(記号付き・素の title で入る)が先にあっても、(1)(3) の対象にならない。PR2.1 と順序を問わないための保険 +- **`task_table_index = task_id` の条件**: PR2.1 の行(記号付き・素の title で入る)が先にあっても、(1)(3) の対象にならない。PR2.1 と順序を問わないための保険。順位はコンテストの全行で付け、更新だけをこの条件で絞る。順位を付ける側でも絞ると、記号の行と問題 ID の行が混ざったコンテストで A から振り直してしまう(Codex のレビューで指摘) - `task_id::int` は安全(範囲の問題 ID はすべて数値)。1 コンテスト最大 14 問なので、`chr(64 + n)` は A〜Z に収まる。SQL の読み方は [investigation.md](./investigation.md#sql-の読み方) #### 確かめ方 @@ -173,6 +175,27 @@ ORDER BY kind; ### 2-6 テスト -- `src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts` の範囲を 145 コンテスト / 1,257 問に広げる。補正していないコンテストは `assignProblemLetters` と一致すること、補正した 8 コンテストは literal の期待値と一致することを見る(M18)。合宿系の title が `${problem id}. ` で始まらないことも見る +- `src/lib/clients/aizu_online_judge/seed_problem_letters.test.ts` の範囲を 145 コンテスト / 1,257 問に広げる。補正していないコンテストは `assignProblemLetters` と一致すること、補正した 8 コンテストは literal の期待値と一致することを見る(M18)。title が問題名(`name`)と同じであることも見る(`${problem id}. ` の付け忘れ・除き過ぎの両方を検出する) - `clients.test.ts`: JAG REGIONAL の取り込みで記号が付くこと。`usesProblemLetters` の JAG の条件を PRELIM だけに戻す変異を検出できること - 書き換えたテストと足したテストに `verify-test-strength` を当てる。期待値を新しい形へ一括置換すると、テストが仕様を検証しなくなるので、`testing.md` の Test integrity に従う + +## 実施記録(2026-10-25) + +コミットは `c605d3bd`(照合スクリプト)→ `4c7b9763`(実装)→ `c835754b`(レビュー対応)→ `597104fd`(スクリプトの削除)。照合スクリプトは `git show c605d3bd:docs/dev-notes/2026-09-09/task-title-index-change-impact/generate_aoj_jag_problem_letters.ts` で参照できる。 + +- **seed の模擬地区**: 本番から取り出した 158 問は、AOJ の fixture(2026-05 記録)と問題 ID・コンテスト・名前がすべて一致した +- **照合**: スプレッドシートの書き写し(66 コンテスト)と 625 問の title が、記号の位置ですべて一致した。補正は 8 コンテスト / 45 行で、目視の暫定値どおり。わざと誤りを入れたコピー 5 種類(欠落の記号を外す、順番の入れ替え、途中の title の誤り、欠落の数の不足、切れた title の前半の誤り)は、すべて検出された +- **ローカルでの移行**: 管理画面で模擬地区を取り込み、本番と同じ状態(16 / 158、index は問題 ID のまま)にしてから確かめた。`SELECT` のプレビューは 625 行すべてで照合の結果と一致し、適用後もプレビューと 625 / 625 で一致した。範囲外の AOJ(PCK、大学系、PR1 の範囲)は変わらない。`EXPLAIN` は CTE 側の Seq Scan 1 回と、`task_task_id_key` の Index Scan を使う Nested Loop +- **テストの検出力**: 6 つの変異をすべて検出した(JAG の記号を PRELIM だけ / ICPC だけにする、補正した記号を基本ルールに戻す、基本ルールのコンテストで A・B を入れ替える、title に問題 ID を付け直す、模擬地区の記号を問題 ID に戻す)。title のテストは、空の title・別の問題の title・問題 ID の付け直しも検出する +- **title の空白**: ローカルの 3355 にも末尾のタブがあった(取り込みが trim する前に入った行)。本件では直さない([plan.md](./plan.md#スコープ外)) + +Codex のクロスレビュー(2026-10-25): + +| 指摘 | 扱い | +| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Moderate: 取り込みは記号がずれる 8 コンテストでも基本ルールで振る | **対応しない**。M14 の決定どおり。8 コンテストは本番・staging・seed のすべてにあり、取り込みは既存の問題を skip する | +| Minor: title のテストが「問題 ID が前に付いていない」しか見ない | **対応**。「title が name と同じ」を見るテストに変えた(seed の 1,257 問すべてで成り立つ) | +| Moderate(条件付き): (1) が問題 ID の行だけで順位を付けるので、記号の行と混ざったコンテストで A から振り直す | **対応**。順位は全行で付け、更新だけ問題 ID の行に絞った。混ざったコンテストを読み取り専用で再現し、直した SQL が正しく振ることを確かめた。ローカルは移行前の状態に戻してから、プレビューと適用をやり直した(625 / 625 で一致) | +| Moderate(条件付き): `task_id` だけで結合している | **対応しない**。`task_id` は schema で `@unique` | + +残っている確認: staging(PR の CI)と本番で、移行が想定の件数(625 / 45 / 467)で通ること。問題一覧の出典欄の表示。 diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md index 9697b63fc..c6b1d10ee 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan.md @@ -1,6 +1,6 @@ # AOJ ICPC / JAG の `problem_index` を A/B/C 形式へ変更する -**日付**: 2026-09-09 作成、2026-10-24 に PR2 以降を再構成/ **状態**: PR0・PR1・PR1.5(#4219)完了。PR2 は着手前の準備が済み、未着手 +**日付**: 2026-09-09 作成、2026-10-24 に PR2 以降を再構成/ **状態**: PR0・PR1・PR1.5(#4219)完了。PR2 は実装済み(ブランチ `#4030`、未マージ) このファイルは全体像と、PR をまたぐ決定・手順だけを書く。各 PR の作業はそれぞれのファイルにある。 From 7c68cef67eeedf1232d0be19a3fb4806cd8b0e4d Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Sun, 25 Oct 2026 01:11:48 +0000 Subject: [PATCH 8/8] docs: note folding the PR2 plan into done.md after production Co-Authored-By: Claude Opus 5.5 --- .../2026-09-09/task-title-index-change-impact/plan-pr2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md index e8901aad0..afa3942a5 100644 --- a/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md +++ b/docs/dev-notes/2026-09-09/task-title-index-change-impact/plan-pr2.md @@ -199,3 +199,5 @@ Codex のクロスレビュー(2026-10-25): | Moderate(条件付き): `task_id` だけで結合している | **対応しない**。`task_id` は schema で `@unique` | 残っている確認: staging(PR の CI)と本番で、移行が想定の件数(625 / 45 / 467)で通ること。問題一覧の出典欄の表示。 + +本番に入ったら、このファイルを [done.md](./done.md) にまとめる(PR1.5 と同じく、決定とコードから読めない事実だけを残し、このファイルは消してリンクを張り替える)。