AHC037振り返り
はじめに
Asprova プログラミングコンテスト(AtCoder Heuristic Contest 037)に参加した。制限時間4時間のAHCとしては「短期間」に分類されるもので、正直言うと苦手である。今回も500番台といまいちだったので、解説動画や上位のソースコードを読みながら振り返ってみる。
問題
問題としては、炭酸とシロップを混ぜて
また、後からでてくるが、それぞれの座標は相異なるのと、0になる点が存在することが問題を解くのを容易にしている。
自分の解法
振り返りとして、考えた順に記載していく。
最小値から作っていく
炭酸飲料
最小値を持つ2つを、中間状態を経由して作成する
このため、以下のようにして生成することとした。
-
,\min(A) を持つ未作成の炭酸飲料を\min(B) ,X とする。Y -
の炭酸飲料を(\min(A), \min(B)) として、作成済とする。Z -
,X ,Y をそれぞれ、作成済の炭酸飲料から一番近い炭酸飲料から作成する。Z
最小値から順番に作っていく (Score: 23,064,446)
赤い点がターゲット炭酸飲料で、青い丸が中間状態として作成した炭酸飲料。赤い点が均等にばらつきがあるため、青い点は対角線上に並んでしまっており、効果的に中間状態を作成していないように見える。振り返って見ると、次に作らないといけないのは、
最も近い炭酸飲料から作成する
上記の出力結果を見ると、作成順など関係なく、純粋に「一番近い炭酸飲料」から作ればいいような気がする。そこで、
最も近い炭酸飲料から作る (Score: 22,803,065)
スコアとしては 0.2M程度下がったが、ベースとしてはこれで良い気がする。
中間状態を乱数で追加する
この後、中間状態となる炭酸飲料を追加していくが、ターゲット炭酸飲料と炭酸もしくはシロップの量が等しいものしか意味がない。つまり最大で
乱数で中間状態を作る (Score: 23,836,311)
最初のものとそんなにスコアは変わっていないが、結局時間切れでこれが提出版となった。
延長戦
2つ以上の炭酸飲料を作る時に中間状態を生成
乱数でおこなうよりは、最も近い炭酸飲料を結んだ木を最適化する方が良いのでは?と考えた。
最初の図に書いたように、2つの炭酸飲料を作るときは、それぞれの最小値の点を経由した方がコストが低いので、以下のようにして中間状態を作る。
- 1つの炭酸飲料から複数の炭酸飲料を生成している場合、
の炭酸飲料を(\min(A), \min(B)) として生成し、それをベースに他の炭酸飲料を生成するようにする。Z
2つ以上の炭酸飲料を生成するときに中間状態を作成する (Score: 28,149,357)
スコアとしては大きく上がった。ただ、1つの炭酸飲料から3つ以上の炭酸飲料を生成している箇所もあり、そこは改善の余地がある。
3つ以上の場合の対応
3つ以上の場合は以下のように処理することとした。
-
,\min(A) を持つターゲット炭酸飲料を\min(B) ,X とする。Y -
の炭酸飲料を(\min(A), \min(B)) として、生成する。Z -
以外の炭酸飲料は、X, Y ,Z-X の線上に中間状態を生成し、近い方から生成するZ-Y
3つ以上の炭酸飲料を生成するときに、2つずつに分ける (Score: 31,182,689)
時間内にここまでいけたらなと思う。2分木にした方がスコアが低くなるのは最初に気づいていたのだから、分割するように考えていれば辿り着けたと思う。
解説動画を見て
小さい二分木を組み合わせる
自分のアプローチはとりあえず木を作ってそれを2分木に集約していく方法をとっていた。一方、解説動画を見ると小さい2分木を作ってそれらを組み合わせる方法が紹介されていた。2つの炭酸飲料
このコストが小さい順に2分木を作っていくと最終的には大きな木ができる...となるはずであるが、実はこの方法はうまくいかない。
小さい二分木を組み合わせる (Score: 23,324,861)
一部「うまく周りとくっつけなかった」炭酸飲料が残ってしまい、長い線ができてしまっている。とはいいつつ私の提出版とそんなに変わらないスコアとなっているのが悲しい。
右上から生成する
そこでバラバラに作成されないように右上の二分木程コストが小さくなるような項を追加する。具体的には前述のcostに対して以下の項を追加する
それは計算すると以下のようになる
このコストが小さい順に処理をする。つまり、
2分木を作っていくと最終的には大きな木になる。ちなみに、
右上から二分木を作る (Score: 35,665,958)
ビームサーチ
上の方法では「もっともコストが小さい木」を順に作っていくが、「コストの小さい
「コストの小さい二分木」を見つける方法は
- ノードを
の大きい順にソートするA_i + B_i - 上位
個のノードを切り出す。M - ビームサーチを1ステップ進める(2個ノードを消して、1個ノードを足す)
- 2.での残りノードから1個入れて3.に戻る
こうすると「コストの小さい二分木」は
ビームサーチ (Score: 36,150,291)
おわりに
なんとなく、焼きなまし法や山登り法のイメージを持って取り組んでしまったため、乱数を使った解法にこだわってしまい、時間を浪費してしまった気がする。上位の方々はビームサーチの後に焼きなまし法を適用していたりするが、まだまだだなぁと感じた問題であった。
Discussion