ICFPC2022 参加記

ICFPC2022に出場しました。 結果は151人中33位でした。(順位表)

私が知る限り日本からの参加者はほぼ全員私より上ですが、あまりICFPCに関する記事は多くないように思いますし、折角なので参加記を書くことにしました。ためになる内容ではありませんが、楽しんで頂ければ幸いです。

問題概略

Specification等は主にこちらで公開されています。

特に、このpdfが最終版のSpecificationです。

細かい仕様等は省きますが、概要だけざっくり述べると以下の通りです。

  • 縦横400pxの画像が目標として与えられる
  • 同じく縦横400pxのcanvas(1つの真っ白なブロック)を、以下の操作を用いて出来るだけ少ないコストかつ高いクオリティで目標の画像に近づける
    • Line Cut Move 縦方向もしくは横方向にブロックを分割し、新しいブロック2つにする 基本コストは7
    • Point Cut Move 1点を中心としてブロックを四象限に分割し、新しいブロック4つにする 基本コストは10
    • Color Move ブロックを任意の色で塗る 基本コストは5
    • Merge Move 同じ形の隣接したブロック同士を交換する 基本コストは3
    • Swap Move 同じ辺の長さの隣接したブロック同士を結合する 基本コストは1
  • さらにこれらのコストは、操作ごとに size(canvas)/size(block) が乗算される(つまり、ブロックのサイズに反比例する形でコストが増えていく)
  • 最終的なスコアは、これら操作群によるコストの値と、目標画像との類似関数の値の和で、これを最小化することが目的

(ICFPCは例年仕様の追加や変更が行われるのですが、私はそれらを全く追いきれなかったので、それらは省略します)

与えられた画像の一例は以下のようなものでした。

ICFPC2022_Problem_Pictures.jpg

私の推し画家であるアルチンボルドの絵(下段右)も入っていて、テンション上がりました。 また、中段右の青い画像は、ゴッホの『星月夜(ほしづきよ)』だそうです。よく目にする画像ではありますが、今回初めて名前を知りました。英語では”The starry night”というらしいです。かっこいいですね。

解法概略

私の解法の概略を述べます。

非常に重要な点として、各操作のスコアは size(canvas)/size(block) が乗算されるということがあります。具体例をあげると、一辺10pxの正方形は、$\frac{400\times400}{10\times10}=1600$ 倍になります。最終的なスコアは大体10000から40000程度になるので、このサイズの正方形に色を塗るだけで、もうほぼその値になってしまいます。 (極めて当たり前のことですが、私は中々この事実の重大性に気付けませんでした)

この性質により、大きいブロックを保持したまま操作していくのが大切なので、 Line Cut Move or Point Cut Move (細分化) → Color Move (色を変更) → Merge Move (元のサイズに戻す) という操作を繰り返すことで、そのことを達成しています。

特に、Cutのパートに関しては、以下のように2回のPoint Cutを使用することで、任意の矩形領域を塗ることが出来ます。(あえて小さめに矩形領域を表示していますが、先述の通り、このサイズの矩形領域は実際には作りません)

cut_part

また、矩形領域の辺がcanvas全体の外枠に接する時にはCutの回数を減らすことが出来るので、それらを丁寧に場合分けしながら、上記のCutの種類やMergeの順序などを適切に試すことで、出来る限り少ないコストで矩形領域を塗る関数を実装しました。(これの実装にはかなり苦労しました。)

そして、この関数を使用する上で、どの矩形領域を塗っていくかということは本問において本質的になります。

ランダムな矩形領域を試して、最もスコアが改善するものを貪欲に選択していくというアルゴリズムを最初に試しましたが、それでは色々と無駄が生じます。

例えば、以下の画像が入力の場合を考えてみます。 9.png

直感的には外側から白色→黒色→灰緑色→モナリザの部分と塗っていくのが最適そうに思えますが、このような単純なアルゴリズムでは先に形としてまとまっているモナリザの方を塗ってしまったり、灰色と黒色の中間色で部分を塗ってしまったりします。

特に、以下のチェス盤を、白と黒の中間色である灰色で塗ってしまうという実行結果が多発しました。

ICFPC2022_Problem1.jpg

このことを改善する案を色々考えましたが、私にはどうにも思いつかず、結局手動ツールを作成して手動でその矩形領域を決めるという方針をとりました。

矩形領域の決め方は、モナリザの例のように外から中へということも注意すべきですが、後から上書きされるなら出来るだけ大きな矩形領域の方が良いという事にも注意する必要がありました。

以下がその一例です。

ICFPC2022_Problem2.jpg

上段では、ロボットの足をそのままの形で塗っていますが、下段では足をもっと長く描いて、その後胴体部分を上塗りすることにより、小さい矩形領域を扱うということを避けています。

このことによって、操作にかかるコストをより抑えることが出来ます。

(また、この手動解を作成していく中で、さらなる改善に気付いてしまいました。上位陣がとっていたDP解と似た方針です。手動でやっていく中で、こういうアルゴリズムを上手いことを実装すれば良かったのかとなりましたが、流石にそれをする時間は全くありませんでした。)

そして、更にその手動解を乱択山登りで改善していきます。

例えば、手動解では当然ブレが存在するので、先のチェス盤などだと線が少しズレるということが発生してしまいます。人の目では完全に一致しているように思えても、類似関数の値は0でないということが良くありました。

そこで、手動解自体にランダムな変形を加えてスコアがどうなるかを見るという、乱択山登りを行いました。また、山登りと言っても、手動解には先程述べた貪欲アルゴリズムをプラスしているので、各実行にも少しブレがあり、かなり楽々と局所最適解は抜け出してくれているようでした。

これらを合わせると、チェス盤の出力は以下のようになって、スコアは20596でした。

image.png

ちなみに、参加者全体のベストスコアは5013らしいです。エグ…

なお、手動解を改善する乱択山登りには、色に関するランダム性も追加しています。 例えば、以下のTheの部分が一番分かりやすいと思うのですが、この部分は目標画像の色よりも少し薄くなっています。(他の色も、スポイトツールで見ると実はかなり違います) 色を背景まで考慮して塗るようにすると、よりスコアが改善されました。

ICFPC2022_Problem8.jpg

私の方針としては以上です。

結果

問題は全部で40題ありましたが、その内私が主に取り組んだ、1日目の時点(Lightning Divison)で公開されていた25問に関しては、以下のスコアが得られました。

seedscoreseedscoreseedscoreseedscoreseedscore
12059669301113607016272192125295
25627722274121001617421792227297
315575822337131404318414352331036
418468913964142617419337742423799
5188231031342153064920249002534244

これらの合計値は606437です。