The 2024 ICPC Asia Pacific Championship 参加記

この記事は、2024/3/2 にベトナムハノイで開催された 2024 ICPC Asia Pacific Championship の参加記です。自分はチーム GoodBye2023 として Yu_212 や shibh308 と参加しました。

Championship とは ICPC の Regional 大会の結果によって招待される Asia Pacific Regional 全体の大会で、World Finals の進出者を選抜するために開催されます。この制度自体は 2020 年からありましたが、情勢の影響で開催が見送られ続けた結果今回が初めての開催となっています。自分たちはいままでは World Finals を狙えるほどの実力があるチームではありませんでしたが、この制度によって World Finals への進出が十分現実的なものとなりました。そのため、我々は真面目に World Finals 進出を目指して練習を積み重ねました。この記事は、その練習と実際の Championship の様子を記した参加記となります。

他のチームメイトの参加記:

yu212.hatenablog.com

AI が考えた参加記のタイトル。DOMjudgeとの一戦って何?

事前準備

作戦

3 人に大きな実力差があるチームではなかったので、事前に役割分担を設けることはしませんでした。ただ、チーム内で一番競技プログラミングが得意なのは Yu_212 だったため、Yu_212 の頭をできる限り働かせることを意識した立ち回りを心がけました。そのため、ライブラリの写経は主に shibh308 が、shibh308 がやることがあった場合は自分が担当することになっていました。

また、初動については予め細かく決めておきました。Championship では横浜のように序盤だけ難易度順といったことがなさそうなため、序盤に複数問問題を読んでおくことになりそうです。自分と shibh308 が比較的英語を読むのが得意だったため問題を読むことに注力し、その間にPC のセットアップを Yu_212 に行ってもらうこととしました。

GitHub の Issue にまとめた開始時にすることリスト

また、問題数が多く問題の把握ができないことが多かったため、問題の簡単な概要と誰が読んだかを記すシートを作成して運用することにしました。他には作戦といった作戦は事前に決めていませんでしたが、練習で詰まったポイントはシステマチックに判定できるようにしていました。例えば、「序盤の簡単な問題で誤答を出したり 20 分以上実装が詰まった場合は問題から引き剥がす」、「サンプルが弱い問題は手が空いている人が実装中にサンプルを作成する」といった具合にです。

練習

12 月は月末にある SECCON の決勝大会に注力したかったこともあり、時間はあったものの練習はほとんど行いませんでした。1 月はチームメイトが帰省していたので 7 日の OUPC が初練習となり、その後から本格的にチーム練を開始しました。最初の方はチームとしての動きのミスが目立ったものの次第に減り、15回ほど練習するうちにチームとしての動きのミスはほぼなくなったと思っています。後で数えたところ、合計では 22 回練習を行っていたようです。大学のプリンタは一ヶ月で一人 200 枚しか印刷できないのですが、それを 3 人分 2 ヶ月フルに使ってもなお足りないほどの問題文とコードを印刷することになりました。

練習部屋にある印刷した紙の置き場。自分たちが印刷した紙でほぼ満杯になった。

また、本番の環境が 去年の World Finals の環境と全く同じであるという情報が選手間のチャットで回ってきたため、それに気がついてからは Virtual Box 上にインストールした WF OS を用いて練習を行いました。WF OS のインストールには非常に時間がかかったため、一通りのセットアップまで終わらせた後のイメージを保存しておき、そのイメージをクローンする形で毎回の練習を行いました。このように毎回環境がリセットされているのは、開始時に準備する担当のチームメイトにとってよい練習となったと思います。

Virtual Box の画面。最後の4(+1)回の練習は WF OS で行った。

ライブラリ整備

Championship では、World Finals と同じように持ち込めるライブラリの枚数に制限がありそうでした。今まで用いてきたライブラリは両面印刷で30ページ弱ありましたが、持ち込むライブラリは片面印刷で25ページに抑える必要がありました。よって、今まで持ち込んでいたライブラリから大幅に削らなければなりません。

環境

PDFの生成は、一般に非常に面倒くさいことが知られています[要出典]。そのため、やらなければいけないとは思いつつ面倒でなかなか手がつけられませんでした。1月末くらいに流石にそろそろ整備しないと間に合わないという雰囲気が出てきていたため、重い腰を上げて 1/22 のバチャ終了後に徹夜して大枠を整えました。管理は GitHub の Private Repository で行い、PDF まわりは基本的には迷路さんの方法を参考にしました。ただ、この方法はTeXに依存しているために環境構築が面倒になっています。チームメイトがいじるときにTeXの環境構築などの障壁がないよう、docker さえインストールしてあれば make コマンド一発で PDF がビルドできるような環境も整えました。しかし、手元で make pdf を叩いて PDF を生成したのは結局自分だけだったようです。一体どうして……

しかしこの整備が無駄足だったかと言うと決してそんなことはなく、簡単に GitHub の CI に乗せることができました。これにより、最新版の PDF が常に Release からダウンロードできる環境を整えることができました。整備した理由はPushするとReleaseが走るCIがあるとオタク的にアガるというしょうもない理由でしたが、実用的にも割と役に立った気がしています。最終的に、これは出先でライブラリの内容を確認したり、チームメイトが更新の結果をチェックするための手段となっていたりしました*1

また、CIを整える過程でCIの出力にいろいろな情報を出したほうが便利そうだということがわかったので、合計ページ数やページ数を食っているライブラリなどの情報を出力するようにしました。

持ち込んだライブラリのRelease
中身

今まで使用していたライブラリが beet さんのものだったため、それをベースとすることにしました。とりあえず今持っているコードをすべて突っ込んだところ 110 ページくらいになってしまったため、チームメイトといる / いらないの選別を行ってライブラリ数を減らしていきました。このときに GitHub のProjectsを使って整理したのですが、これがなかなか使いやすかったです。

選別のようす

これでひとまず 23 ページ程度には収まったのですが、必要そうなライブラリを追加していく過程でまた膨れ上がっていきました。結局 27 ページ程度まで膨れ上がりましたが、PDF の各所にあるマージンを徹底的に削ったり、空白や改行を削って一行に押し込んだりする自明な改善で 3 ページほどページ数を削減することで解決しました。

それぞれ改善前 / 改善後

このようなちまちまとした作業は最初にライブラリの環境を整備したのが自分だったためかほとんど自分がやっており、貧乏くじを引いてしまったとよく愚痴をこぼしていました。それはそれとして自分が満足するライブラリを作ることはできたのでよかったのかなとは思っています。

ハッシュ

いままでのライブラリにも写経チェック用のハッシュがありましたが、チームメイトがハッシュの使用に対してかなり強く反対していたために使用していませんでした。しかし、練習で写経チェックの時間が嵩んだことや写経ミスが相次いだことから、何らかの方法でハッシュを導入しないといけないとも考えていました。
チームメイトがハッシュに反対していた理由は主に「C++で書かれたハッシュプログラムの写経に時間がかかる」「for 文を補完で出すなどでライブラリを完璧に写していない場合にハッシュが無効となる」という理由でした。これらについては、行毎にハッシュを出力するシェルスクリプトを書くことで解決し、ハッシュを導入することができました。

ハッシュの手法としては、CLI で簡単に扱えて軽量な MD5 を用いました。ただ、行毎にハッシュ全体を乗せることは到底できないため、各行にはハッシュのうち何文字かだけ乗せることになります。ハッシュの文字数は多ければ多いほど衝突の確率は減りますが、同時に横幅も占有してしまうことになります。なので、写経でミスをした際にどの程度までの確率ならば許容するかを考えながらハッシュの文字数を選定しました。ハッシュは hex で出力されるため、2文字であれば256種類、3文字であれば4096種類の空間となります。これは完全に主観ですが、1/256 の衝突は実際に起こりそうですが、1/4096 であれば起こったら笑い話にできるレベルで起こらなそうです。なので、基本的にハッシュは 3 文字にすることにしました。

また、このハッシュを実際に運用するためにはライブラリ側にハッシュを出力しなければいけません。ライブラリのルールには "Text and illustrations must be readable by a person with correctable eyesight without magnification from a distance of 1/2 meter." と記されており、あまりにも小さい文字では持ち込むことができなさそうです。correctable eyesight の意味がうまく取れませんでしたが、「矯正して到達可能な視力の人間が 0.5 m から読めるサイズ」と解釈し、裸眼の視力がかなり悪い自分が矯正後に 50 cm 離して余裕で読むことができるサイズに抑えました。そうはいっても多少は怖かったため、6 pt の 3 文字バージョンと、9 pt の 2 文字バージョンの 2 部を持ち込みました。

ジャッジ環境の調査

DOMjudge の調査

ICPC では、DOMjudgeというオープンソースのジャッジ環境を使用することが一般的です。このジャッジが CodeforcesAtCoder といった慣れ親しんだジャッジと違う挙動をしている可能性に惑わされることがないよう、事前に DOMjudge のコードを実行する部分を読んで知識をつけておくことにしました。……というのは建前で、実際のところは ICPC 練習で CTF などへの参加を諦めていたためにパソコン欲が満たされていなかったことによる一種の発作のようなものだったと思います。

まずは DOMjudgeを自分のPCで動かすことにしました。このようなソフトウェアは動かしにくいイメージがあったのですが、少し調べるとDocker のイメージが公開されており比較的簡単に動かせました。

DOMjudgeのAdmin側画面のようす

DOMjudge はWebアプリ側を担当するサーバー(domserver)と、ジャッジを担当する judgehost と呼ばれるサーバーとに分かれています。judgehost は複数台登録することができ、これを用いて多くの提出を並列に捌くことが可能となっています。これらのうち、今回興味があるのは judgehost の、特に提出したプログラムを実行する部分です。これはrunguard.ccというプログラムと、それを実行するtestcase_run.shというシェルスクリプトが担当しています*2

これらを眺めると、runguard はコンパイルされたバイナリに各種制限をつけた上で実行しており、testcase_run.sh は runguard の実行結果が書かれた program.meta というファイル及び output を解析して verdict を返すスクリプトであることが分かります。

次に、 runguard が何をやっているかを詳しく読みました。このプログラムは先程も述べたとおり、バイナリに制限をかけた上で実行するものです。この制限 は setrestriction という関数で主に行われています。このコードを読むと、どのような制限がかかっているかを詳しく理解することができました。制限の値は runguard を実行する際のパラメータで指定できるのですが、DOMjudge の標準で指定されているパラメータでは以下のような制約がかかっている(はず)です:

  • chroot を用いた制限
    • プログラム毎に別のルートディレクトリを用いるようにしている
  • ファイルのパーミッションを用いた制限
    • 実行用のグループ及びユーザーを用いることで、ファイルの作成及びすべての書き込みを禁止している
  • rlimit を用いた様々な制限
    • メモリ及びスタックサイズは無制限としている
      • メモリは cgroup を用いて制限している
    • プログラムが書き込めるファイルサイズを OLE の値としている
      • 最大で用いることができるプロセス数を 64 としている
    • CPU 時間を ceil(max(実行時間+1, 実行時間*1.1)) 秒に制限している
      • rlimit では 1 秒単位での時間制限しかつけることができない
  • 自プロセスの cgroup への登録
    • 詳細は後述

また、cgroup と呼ばれる機能を用いてプログラムが使用できるリソースをコントロールしています。これは主にメモリの使用量を制限するために用いられていますが、実行する CPU のコアなどを細かく指定することもできるようです。おそらく、実行されるコアによる不公平さを回避するための機能だったりするのでしょう。

勘のいい方ならお分かりでしょうが、ここまででは実行時間制限に関する厳密な制約は一つも出てきていません。これは、詳細な実行時間は別の部分で管理されているからです。このプログラムは自身を fork することで実行途中から監視用の親プロセスとバイナリ実行用の子プロセスに分岐しています。親プロセスの具体的な役目は、プログラムの終了を待った後に出力を保存し、プログラムの実行結果に関する情報を解析して metafile と呼ばれるファイルに書き込むことです。実行に用いた詳細な CPU 時間は cgroup の情報から得ることができ、親プロセスはこれを用いて TLE などの判定をしているのです。また、一つのプログラムがジャッジを占有してしまうことを防ぐため、ある程度時間が経過した後にプログラムを強制終了する処理も行われています。IO 処理など CPU 以外の部分で時間がかかっていた際に誤ってプロセスを終了してしまうのを防ぐため、プロセスを終了するまでの時間は実行時間制限より多少長めに確保されています。

これまでの流れを簡易的にまとめると、以下の通りとなります*3

parse_option(); // コマンドラインオプションをパースする
open_metafile(); // 実行結果を格納するファイルを作成する

int pid = fork();
if (pid == 0) {
  // 子プロセスの場合だけ実行される
  setrestrictions(); // 自プロセスに制約を追加する
  run_binary(); // バイナリを走らせる
}
else {
  // 親プロセスの場合だけ実行される
  redirect_output(); // 子プロセスの実行結果を保存するファイルを作成する
  
  settimeraction(terminate); // timer の終了時にプログラムを強制的に終了
  settimer(hard_limit); // timer を設定。デフォルトでは max(TL*1.1, TL+1sec)
  
  wait(pid); // 子プロセスの完了を待つ
    
  read_program_output(); // 子プロセスの出力を読み取り、ファイルに保存
  
  check_statuscode() // ステータスコードを元に RE をチェック
  output_cgroup_stats() // cgroup の情報から TLE や MLE をチェックし、結果を metafile に書き込み
}

このような調査をしている最中、このプログラムに潜在的脆弱性があることに気が付きました。これは、fork の前に open_metafile が実行され、open されたファイルが run_binary の前に close されていないことで発生しています。Linux では、fork や別プログラムの実行を行っても開いているファイルは子プロセスや別プログラムに受け継がれます。そのため、本来ならば監視プロセスしか書き込むことができない metafile にジャッジ対象のプログラムが書き込むことができてしまいます。これを用いると、metafile を改ざんして実行結果を偽装することができてしまいそうです。

ここからはこの脆弱性もどきについての話をしますが、これはしかるべき窓口を通して報告をした結果修正パッチがすでにリリースされており、公知のものとなっていることは申し添えておきます。

真っ先に思い至ったことは、実行時間制限を一秒程度伸ばすことができる可能性についてでした。前述した通り、プログラムが強制終了されるのは実際の実行時間制限より少し長い時間が経過した後です。そのため、どうにかして TLE であったという事実を覆い隠すことができれば実行時間制限を超えて実行できそうです。しかし、結論から言えばこれを行うことは不可能そうでした。なぜならば、実行時間制限を超えたことはあるパターンにマッチする文字列が metafile 内に存在するかで判定されており、かつプログラムが完全に終了された後に metafile を書き換えることが不可能だったためです。ただ、metafile に特定の文字列を書き込むことで、どんな実行時間のプログラムでも TLE として処理させることができそうです。これにより、夢の(?) 1 msでTLEを取得することが可能となりました。

プログラムが完全に終了された後に metafile を書き換えることが不可能であるということは、既存の metafile の先頭にコンテンツを追加することのみが可能であるということです。metafile のパースが tag, content = line.split(": ") のように行われているため、通常 metafile の 1 行目にあるメモリ使用量の tag を破壊して任意のメモリ使用量に置き換えることができそうです。これを用いることで、ありえないメモリ使用量(負など)を申告することができました。
また、普段は文字列が入ることが想定されていない特殊なタグに対して文字列を挿入することも可能となりました。例えば、送出された signal を示す tag に文字列を入れることで DOMjudge の提出詳細画面に任意の文字列を表示させることが可能となりました。ここで XSS などができれば立派な脆弱性だったのですが、Webフレームワークが入力文字列をエスケープしていたことにより XSS を行うことはできませんでした。他にも ReDoS などについても考えましたが、 metafile が通される正規表現はどれも ReDoS を行うことが不可能なものだったために成立しませんでした。

このバグを用いて改ざんした提出結果

このように全くもって悪用することが不可能そうなバグでしたが、潜在的に危険ではあることは確かなために DOMjudge のセキュリティ報告窓口を通じて報告しました。その日のうちに対応して下さり、とても有難かったです。

ジャッジサーバーの調査

競技の10日前ほどに Judge Manual が公開され、そこには実際に使用される GCPインスタンスが記されていました。そのため、実際に同じインスタンスを借りて様々なコードの実行速度を調査しました。実行したコードは unordered_map やmap に要素を追加するコードや bitset のコード、そして各配列要素に対して xorshift のようなことを行うコードです。特に後者二つについては SIMD 化の影響をテストしたかったため、pragma を追加した場合とそうでない場合それぞれについてテストしました。また、それぞれのコードはAtCoderおよびCodeforcesでも実行し、慣れ親しんだジャッジとの速度の差についても意識できるようにしました。実行結果(ミリ秒)は以下の通りです*4。なお、実際に実行したコードはgistに公開しています。

unordered ordered vectorize vectorize_opt bitset bitset_opt
AtCoder 323 1104 - 720 - 656
Codeforces 732 1466 5506 717 5506 889
GCP 491 1190 1329 386 1694 479

これを見ると、SIMD化が関わらない場面ではAtCoderのジャッジとほぼ同等、SIMD化が関わる場面では2倍弱程度の高速化になっていることが分かります。そのため、Codeforcesで行っている普段の練習のような感覚で提出しても問題ないことが分かりました。

ただ、これは「本番のジャッジ環境がPDFに記されている通りであれば」の話です。今回の運営は横浜のような手練れの方々ではなさそうなため、掲載されているPDFはあまり考えず何らかのファイルをコピーしてきているだけという可能性も十分にあると考えていました*5。そのため、本当に示された通りの環境で動いているかをPractice中に確かめるためのコードを事前に用意しておくことにしました。

普段であれば /proc/cpuinfo に特定文字列が入っているかを確かめるコードを書けば十分でした。しかし、今回は前項で触れた通り chroot によってファイルにアクセスできないようになっているためにそれを行うことはできません。追記: procfs はマウントされていました。なので、普通に assert(WEXITSTATUS(system("grep 'XXX' /proc/cpuinfo")) == 0); とすれば良さそうです。CPUID命令というCPUに関する様々な情報を取得することができる命令を用いてCPUのブランド文字列を取得し、それが事前にチェックしていたものと等しいかどうかを確かめるようなコードを用意しました。コードは以下のような形となりました。

#include<stdio.h>
#include<string.h>
#include<assert.h>
int a[4];
void brand_string(int eax) {
  if (eax == 1) {
    __asm__("mov $0x80000002 , %eax\n\t");
  }
  if (eax == 2) {
    __asm__("mov $0x80000003 , %eax\n\t");
  }
  if (eax == 3) {
    __asm__("mov $0x80000004 , %eax\n\t");
  }
  __asm__("cpuid\n\t");
  __asm__("mov %%eax, %0\n\t":"=r" (a[0]));
  __asm__("mov %%ebx, %0\n\t":"=r" (a[1]));
  __asm__("mov %%ecx, %0\n\t":"=r" (a[2]));
  __asm__("mov %%edx, %0\n\t":"=r" (a[3]));
}

void get_brand(char* buf) {
  int* ibuf = (int*)buf;
  brand_string(1); memcpy(ibuf+0, a, 16);
  brand_string(2); memcpy(ibuf+4, a, 16);
  brand_string(3); memcpy(ibuf+8, a, 16);
}

int main(){
  char buf[12*4+1]{0};
  get_brand(buf);
  char* expected = "           Intel(R) Xeon(R) CPU @ 3.10GHz";
  assert(strlen(expected) == 41);
  assert(strcmp(expected, buf) == 0);
  puts(buf);
}

このCPUはGCPのマニュアルにも具体的なモデル名は書かれていませんでしたが、CPUのブランド文字列を見る限りカスタムモデルとなっているのだと思います。

コンテスト

力尽きてきたので軽めに書きます。他のチームメイトが詳細に書いてくれるでしょう。(参加記とは……?)

Practice

Practice Session は開会式を終えて昼食を食べた後に行われました。我々は事前に Practice でやることを GitHub に纏めていたのですが、Practice ではドキュメントと医療品以外は持ち込み禁止とのことだったので急遽予備のライブラリ群に書き写して持ち込むことにしました。ハンドクリームやウェットティッシュ等が許可されているかが不明でしたが、堂々としていたら持ち込むことに成功しました。

セッション中に日本でむちゃくちゃ買い込んできたお菓子を明日持ち込めるか聞いたところ、する場合は今日中に入れておく必要があるとのこと。ホテルにあるから今すぐ帰るべきか?と若干の圧をかけると、当日の持ち込みも許可されました。同様に同一のライブラリを 3 部まで持ち込んで良いと言われたので「それは事前に告知されていなかった、不公平だ」とゴネたところ印刷してもらえることになりました。むちゃくちゃ負荷をかけている気がして少し申し訳なくなりましたが、まあ事前にこういう部分を公開していない方も公開していない方だよね……ということで。

しばらくするとコーチ陣が電子機器などを携帯して入ってきていたので、あの書き写した努力は一体何だったのかという気持ちになりました。後半は言えば電子機器を持ち込めたのでしょうか……。

コンテスト中

  • 開始前: 風船の色数を数えていました。自分たちは 12 色という結論に至りました。
  • [0:00]: 事前の相談通り、Yu_212 がパソコンのセットアップをしている間に自分が前から、shibh308が後ろから問題を読みました。読んでいる間に shibh308 が H に簡単枠がありそうということで Yu_212 に伝え、実装していました。
  • [-0:10]: ちょうど実装が完了したくらいで、自分と shibh308 を合わせてすべての問題が読めている状態になりました。提出前に shibh308 がコーナーケースを指摘して修正などをしつつ、提出をしました。が、その提出は WA となりました。
  • [-0:20]: このような状況では他の人と交代すると決めていたため、コードを印刷して自分がデバッグをすることにしました。印刷を待っている間に解けそうな問題(C, D, E)を Yu_212 に説明し、説明が終わったくらいのタイミングで印刷が到着。少し眺めた後にバグがわかったので、Yu_212 に修正してもらいました。それを提出して AC。
  • [-1:00]: この段階で C が解けそうに見えてきたため C に集中することにし、D と E を yu_212 に任せます。暫くすると J が Yu_212 によって解けていて、実装を Yu_212 が shibh308 に任せていそうな雰囲気が伝わってきました。shibh308 がそれを実装した上で提出し、AC。
  • [-1:15]: 確かこのあたりで C はほぼ解けていて、提出をし、assertion で RE を出すみたいなことをしていた気がします。
  • [-1:30]: このあたりでさっき投げた E が解かれていました。Cはまだバグっています。本当に C で沼に嵌っていたため自分から Yu_212 に Help を出し、一旦デバッグを交代してもらう代わりに結構解かれていた G などを考えることにします。
  • [-2:20]: トイレに行ったら C のバグのケースがなんとなく分かったので帰って報告すると、Yu_212 も同時に同様のケースを見つけていたよう。ここからは自分だけで修正できそうなので Yu_212 は他の問題に行ってもらうことにして、修正しました。提出したら AC。
  • [-2:40]: とりあえずとても解かれている G を考えながら何もわからないなあと言っていたら、shibh308 が解けたらしいので解法を聞きます。合っていそうで実装で詰まる要素もあまりなさそうなので、とりあえず実装してもらうことに。やる問題がなくなったので、解くことがほぼ必須になりそうな F を考えることにします。
  • [-3:00]: F を考えていたところ、なんとなく解けていそうな雰囲気が漂ってきました。それはそれとして解法が簡単すぎたので一回 shibh308 に確認を取ると、問題を途中で取り違えていたことに気が付きます。気を取り直してもう一度考えると、もう一度解けていそうな雰囲気が漂ってきました。今度は合っていそうだったため、実装を詰め始めました。このタイミングで K が解けたり解けていなかったりしていた気がします。
  • [-4:00]: K の実装が詰まったタイミングを見計らって、F の実装を開始しました。あと 2 時間で K と F を 2 問通さなければいけないという多少ハードな状況だったため、速度は求めずに安全側に倒した実装を心がけました。具体的には、愚直解の一部を書き換えれば AC が取れる解になるタイプの問題だったため、まず愚直解から書き、その解の二乗を線形に落とすような修正を加えるという方法を取りました。これをすると、バグった際にランダムテストを回しやすい実装となります。実装できたので提出しますが、TLE。入出力高速化が何故か消えていたことが shibh308 に指摘されたのでそれを修正しましたが、なおも TLE でした。すべての関数呼び出しを削除し、vector を生配列に修正して提出すると、WA となりました。
  • [-4:55]: K のデバッグが最優先だったため、F は印刷した紙の上でデバッグすることに。K はもうペナルティを気にする段階でなかったため assert を用いてデバッグしていて、その間に自分が F を紙の上でデバッグしました。一箇所で参照する配列を間違えていたことに最後の 6 分くらいで気が付き、パソコンを 30 秒奪い取って修正し、提出をすると AC。
  • [-5:00]: もう自分ができることはなくなりました。あとは Yu_212 が K を合わせてくれることを祈りながらタイマーを眺めていましたが、最後まで合わずに競技時間が終了。

結果として、26位(大学別順位18位)という自分たちからすれば失敗の結果に収まりました。例年は Asia から 16-18 チーム程度の World Finals 進出チームが出るため、ギリギリで落ちている可能性が濃厚という成績のようです。

コンテストワクワク要素
  • 机の配置で文字を書いていました。自分たちは被害を被るような場所ではなかったので、あまり文句は言いません。
  • 問題を後ろから読む担当の shibh308 の問題文から最後の問題が抜けていました。幸い最後は難しめの枠でしたが、これで簡単枠だったら割と悲惨なことになっていたと思います。
  • 競技中にシェルの履歴を遡っていたら Practice 時の履歴まで遡れてしまって戸惑いました。CLion の shell だけ変なところに履歴がありリセットし忘れたみたいな話かな?と思いましたが、競技後に確認すると .bash_history に昨日の履歴が残っていました。
  • 向かいの Bocchi The Tech の声が壁越しに貫通してきて、普通にところどころ何を言っているか聞こえる場面がありました。具体的には、問題管理シートを書いていた Yu_212(12 問だと思い込んでいる)が 13 問らしいという情報を得る、高度合成数の話をしているのが聞こえてくるなどがありました。
  • 昼食のサンドイッチを配ってくださっていた方が、コンテスト中にカントリーマアムを指差して "Is this your countrie's cookie?" のようなことを話しかけてきた気がします。本当にびっくりしましたが、とりあえず適当に返していたら "Did you bring it from your country?" みたいなことまで聞いてきて「普通に会話する気あるんだ……」ともう一度びっくりしました。早く会話を終わってほしそうな雰囲気を出していたら次に行ってくれました。
  • どこかの国のコーチ二人が選手も使うトイレでタバコを吸いながら普通に会話していてびっくりしました。

コンテスト後

K が本当になぜ通らなかったのか分からなかったので、提出したコードを写真に撮ったうえでホテルに帰ってから OCR をしてコードを復元し、CF のミラーコンテストに提出してデバッグすることにしました。ここでそもそも入力からオーバーフローしていることに気が付き、一同驚愕……。#define int long long をしたところ無事通りました。悲しいね。

翌日のクルーズでは Speed Star の方々や日本から Technical Comittiee や作問として関わっている方とお話しさせて頂きました。ICPCの昔話や苦労話などいろいろなお話が聞け、とても刺激的でした。特に自分はジャッジシステム側の構成に興味がある身だったので、実際のオペレーションや構成がどうなっているかの話を(もちろん言える範囲で)聞けたのはとても興味深かったです。我々学生がこうして ICPC を楽しめるのは、本当にいろいろな方の尽力のお陰だというのを改めて感じました。

来年への抱負

去年までは完全に競技プログラミング引退老人卍という気持ちだったんですが、まだ World Finals も狙えそうだし現役競技者だぜという気持ちになったのでこれからも競技プログラミングを続けたいと思います。目指せ World Finals!

*1:手元でチェックするほうが簡単だと思うんですけれどもね……

*2:他にも domserver と通信をしてtesctase_run.sh を実行する judgedaemon.main.php も面白いといえば面白いですが、あまり本質的な情報はなかったために今回は省略します

*3:リソースの後処理などは除いています

*4:AtCoderはすでにpragmaとほぼ同等の最適化が行われているために pragma なしは省略

*5:実際はジャッジの準備などは横浜の方々が行ってくださっていたようです。本当にありがとうございます。そのため、PDF なども環境に合わさったものとなっていました。