AtCoder Heuristic Contest 048お疲れ様でした。相対スコア858,336,697,107点で2回連続の5位です!
今回も解説記事を書いていきます。
問題概要
絵の具を混ぜて欲しい色を作ってね。
解法
概要
の値によって4通りの解法を使い分けました。
が中途半端な値の場合、途中まで上位解法を行った後、ターンが足りなくなったら下位解法に切り替えました。
: 固定パレット上で混ぜる
: 1色1レーン使って、4色を
(
は定数)ずつ混ぜる
: 1色1レーン使って、4色を
ずつ混ぜる
: 1色1レーン使って、4色を
(
は定数)ずつ混ぜる
前提
指定された色 を作っていく問題ですが、各色を3次元空間上の点だと思うと見通しが良くなります。
絵の具の色を表す点 を適切に4色選ぶと、四面体
内部の点は(任意の実数比で混ぜ合わせることができれば)自由に作ることができます。
作成したい色を表す点を とすると、点
が四面体
の内部にあれば点
を作ることができます。これは四面体
の体積と、4つの四面体
の体積の和が一致しているかどうかを調べれば良いです。*1 *2
このときの四面体 の体積比を
とすると、絵の具
を
の割合で混ぜることで所望の色
を得ることができます。*3 *4
ということで、 が大きいときはできるだけこれの達成を目指します。
① T~4000くらいのとき
が小さいときは上記の4色混ぜをするだけの手数を確保できないので、たくさん小部屋を作って適当に混ぜました。
レーンを 本用意して、レーンを縦に2分割したセルを作り、各レーンの中で絵の具を混ぜました。操作の候補として、
- レーン
に対して、仕切りを動かさず、0, 1, 2, 3, 4g の絵の具を足してから採取する
- レーン
に対して、仕切りを削除し、適当な場所に動かした上で、0, 1, 2, 3, 4g の絵の具を足してから採取する
を使い、ビームサーチしています。
絵の具をどのように足せば良いかについて考えます。セルに既に入っている絵の具の色ベクトルを 、量を
とし、目標とする色ベクトルを
とします。このとき、追加する絵の具の量を
とすると、追加したい絵の具の色ベクトル
は、
より、
となります。あとは gの絵の具を混ぜてこれに近い色を作る方法を高速に探せばよく、予め
gの絵の具の混ぜ方
通りを前計算してkd-treeに格納しておくと、
に近いいい感じの色を
で検索することができます。*5
② T~13000くらいのとき
が大きくなると少しできることが増えてきます。ここから、前述の4色混ぜを行っていきます。
まず 本の絵の具のタンクを用意し、それぞれに1gずつ絵の具を入れておきます。各タンクから好きな量だけ絵の具を取り出すことができれば、所望の色を作り出すことができます。
これを実現するため、下図のようにタンクの内側に仕切りを追加してから外側の仕切りを開けるという操作をします。これにより、タンクの中の絵の具を 刻みで取り出すことができます。なお、絵の具が足りなくなったときは仕切りの位置を最大までリセットしてから1g足します。
これで各色を好きな量だけ取り出すことができるようになりました。これを4色について繰り返して混ぜます。欲しい量ピッタリ出すことは基本的にできないので、色ごとに欲しい量より小さい側と大きい側の2通りを考え、bit全探索で 通り全部調べることにします。
これを使って、各目的色に対して4色ずつ選んで混ぜることを繰り返すビームサーチをします。*6 なお、混ぜて採取した後の絵の具が微妙に残ってしまいますが、私の解法では毎回余った分は捨ててしまいました。*7
これにかかる手数を考えます。目的色1色あたりにかかる手数を考えると、
- 絵の具を足す: 平均
手(合計で
回くらい追加するため)
- 絵の具を採取する:
手
- 絵の具を捨てる:
手
- 絵の具を出す量を調節する:
手(1色あたり
手を
色分)
- 絵の具を足すときに仕切り位置のリセットを行う: 平均
手
となるため、 が
くらいからこの解法が使えることになります。
③ T~19000くらいのとき
がさらに大きくなると、より精緻に取り出すことができるようになります。下図のように、一旦タンクの長さを伸ばしてから取り出すことを考えます。
タンクの長さを 、現在の絵の具が占めるマスの数を
とすると、
であるような有理数
を作ることができます。このようにすると、取り出す量を概ね
刻みくらいのオーダーで制御することができ、精度が向上します。
もう少し詳しく解析しましょう。簡単のため のケースを考えると、
を満たす既約分数
の数は、オイラーのトーシェント関数を
として、
と表すことができます。これは
と近似できる*8 ので、ざっくり平均
刻みくらいで量を調整することができることになります。*9
この解法にかかる手数を考えます。目的色1色あたりにかかる手数を考えると、
- 絵の具を足す: 平均
手(合計で
回くらい追加するため)
- 絵の具を採取する:
手
- 絵の具を捨てる:
手
- 絵の具を出す量を調節する:
手(1色あたり
手を
色分)
絵の具を足すときに仕切り位置のリセットを行う: 平均← これは不要手
なので、 が
くらいからこの解法が使えることになります。
④ T~31000くらいのとき
がかなり大きいときはもう1段階ステップを増やしました。
下図のように、各タンクの片側だけでなく両側から出せるようにしておきます。左側から大雑把に出して、必要に応じて右側への「繰り下がり」を行った後、右側で分数方式を使って微調整をする帯分数的な制御をしました。*10
このように制御すると、 を作り出すことができます。かなり良い精度が出せて、色の誤差は
くらいまで小さくすることができます。
AHCは暫定6位でした。めちゃくちゃ自由度が高くて面白かった……!
— TERRY (@terry_u16) 2025年6月9日
4色使うと四面体の内部の色を自由に作れるので、Tが足りていれば目的の色ごとに4色選んで混ぜていきました。いい感じの帯分数x+p/qを作るのが強くて、ビムサを走らせた後に最後を焼きなまして余りが少なくなるようにしました #AHC048 pic.twitter.com/Y6DIIEfND9
その他の工夫
焼きなましによる無駄な絵の具の削減
が大きいケースはできるだけ最後に余る絵の具を少なくしたいので、ビームサーチを1000ターン行った後、最後の20ターンだけ焼きなましを行いました。
十分精度よく絵の具を制御できると仮定して、余った絵の具は毎回捨てることにすると各ターンが独立になるため、各ターンでどの絵の具をどのくらいの量使うかを焼きなましました。
最後の20ターンはビームサーチと焼きなましの両方を試し、良かった方を採用しています。
使用する絵の具数の削減
絵の具は 色与えられますが、パレットの大きさは固定なので、絵の具の種類数が多すぎると1色あたりに使えるマスの数が少なくなり、制御の精度が落ちてしまいます。そのため、解法①以外では不要な絵の具を予め削除しておきたい気持ちになります。*11
3次元空間上で 個の点を考えると、これらの点の凸包を構成する点だけあれば凸包内部の点を全て作ることができます。そのため、そのような絵の具だけを残して他の絵の具をなかったことにしました。*12
3次元凸包の実装は、Codeforcesブログの のアルゴリズムを参考にしています。
感想
自由度がものすごく高くてとても面白い問題でした。個人的には Graphorean の次に好きな問題です。
4色を混ぜるところまでは割とすぐに思い付いたのですが、それだけだと順位がズルズル落ちていったので、どうやって精度を上げるべきか相当悩みました。しばらく 進数を考えていたのですがそこまで精度が上がらず、また手数もかなり消費してしまうので苦しかったです。分数解法は最後の土曜深夜に思い付いたのですが、それで精度が爆上がりしたのがかなり面白かったですね。
これで1ページ目は9回連続となりました。せっかくなら2桁に乗せたいですが、次回は短期コンテストなので相性次第といったところでしょうか。あまり気にせず頑張ります。
*1:ってChatGPTが言ってました。
*2:浮動小数点数の数値誤差には注意してください。
*3:ってChatGPTが言ってました。
*4:連立方程式を立てて解いても良いです。
*5:絵の具の混ぜ方の組合せ数が少ないときは愚直な線形探索の方が高速なので、適宜切り替えました。
*6:「1手操作する」ではなく、「1色分混ぜる」操作列をビームサーチの1ターンとしました。
*7:Dが大きいケースでは1gでも捨ててしまうと上位と大きな差が付いてしまうので、なんとか頑張って捨てないようにすべきでした……。
*8:https://en.wikipedia.org/wiki/Totient_summatory_function
*9:……というのは後付けで、コンテスト中はここまで細かく解析していません。
*10:分数を考える前にもともとk進数で制御する方法を考えていたので、その名残で繰り下がりと呼んでいます。
*11:解法①では4色混ぜるだけの手数の余裕がないので、絵の具の色が多い方が有利かなと思います。
*12:凸包を構成する点も必要なわけではないのですが、そのような点を削除することを失念していました。コンテスト序盤で考察はしていたのですが……。