16.3K Views
September 13, 23
スライド概要
AHC・Kaggleの解法投稿用
AHC023 4位解法+参加記 Shun_PI(@Shun___PI) 1
前置き • AHC023で4位になり、AHC赤色になりました • 解法そのものよりも、 考察過程を中心に説明していきます • 参加していない人も読むことを想定し、 できる限り問題の内容や性質についても 説明していきます • 問題文自体は短いものの性質を理解するのは大変な部類の 問題なので、できる限り説明を頑張りますがついていけなく なってしまったらごめんなさい • 私のコンテスト中の実際の提出内容に沿いつつ、 「貪欲解(41.6M)」「焼きなまし解(44.5M)」 の2種類を時系列に沿って説明していきます 2
問題概要 • 20×20の土地があり、一部は壁で区切られている • K(約5000)個の作物があり、それぞれここまでに植えて いなければならないターンSと収穫しなければならないターンDが 決まっている • 作物を植える→収穫を100ターン繰り返す • ここで、作物を植える・収穫するには、 そのマスから出入り口までが空きマスで 繋がっている必要がある ここが 出入り口 • ただし、あるターンにおいて植える・収穫する 順番は自由に変えられる • 言い換えると、その時に植えた・収穫する予定のマスに 関しては跨ぐことができる • 収穫するターンDは必ず守らないといけないが、 植えるターンはSかそれより前ならいつでもいい • ただし、ターンSより前に植えてもターンSまでの間は 作物が植えてあっても土地の利用率にカウントされない • 土地の利用率を最大化したい • 利用率は、現在ターンよりターンSが前である作物の植わっている期間に相当 3
問題の性質の確認 • 収穫するターンDは必ず守らないといけないので、 Dが大きい作物より奥にDが小さい作物を植えることはできない • 言い換えると、収穫する際はDが小さいものが手前、 Dが大きいものが奥になるように植えればよい • 植えるターンについては、Sより早ければいつでもよいので、 植えられるときに奥から手前に向かって植えてしまえばよい 4
提出1. 手前から奥に収穫するだけ • 開始1時間半の提出(実際はなんかWAになってた) • 489位(21.7M、緑パフォ相当) • アニメーションのリンク • https://twitter.com/Shun___PI/status/1701931235684319402 • 手前から奥に向かって Dの昇順(D同じならSの昇順)になるように 全てのマスに植える • 出入り口からのBFSで計算した距離順に植えるのが楽 • 全て収穫が終わったら再び全てのマスに植える • この戦略は実装が簡単な割にはスコアがでるので、多 くの人が最初に試したと思われる • しかし、全ての作物を収穫しないと次の作物を 植えられないにもかかわらず周期が長すぎて、 明らかに無駄な空きマスが多く発生している 5
通路+グループ方針 • 周期が大きすぎることによる無駄な 空きマスの数を減らすことを考えたい • 周期が大きいのは、1周期で使用する作物のDの 幅が広くなってしまうから • あるDを持つ作物は20~30個くらいなので、400マス全てを 埋めると約15ターン分になり、前に収穫したマスは 14ターンくらい無駄になってしまう • 常に空けておき自由に動ける通路と それに接する小さなたくさんのグループを決める • 十分グループの大きさを小さくすれば常に同じS/Dの 作物を割り当てることでグループ内の 利用率を大きく上げられる • 各グループが独立に管理できるのも良さそう • 現実の農業のアナロジーもあり思いつきやすく、 かなり多くの人がこの方針を使っていた ・小さいグループの内部で作物を 割り付けることで利用率アップ ・常にどのグループにも到達できるので グループが独立に扱えてうれしい 6
通路+グループ方針の実装 • 作物の割り当ては提出1.と同じようにできそうだが、 通路とグループを決める部分を実装しないといけない • これは、BFSを駆使することで実現できる • 通路を木として作るには、 • 始点のみを通路とした状態からスタート • 以下を何回かループ • • • • • 通路マスの中からランダムに始点を選ぶ 始点からBFSする BFSで到達したマスからランダムに終点を選ぶ BFSの経路復元により最短パスを決定 最短パス上を通路に含める • グループを作るには、 • 通路マスに隣り合っている全ての非通路マスを 「グループの根」とする • 全てのグループの根から多点BFSをして、到達できた領域を それぞれのグループの領域とする 全ての始点が 「グループの根」 7
提出2. 木の通路を乱択 • 開始9時間の提出 • 6時間くらい外にいたので実働3時間 • アニメーションのリンク • https://twitter.com/Shun___PI/status/1701932162885492 768 • • • • 260位くらい(36.0M、水パフォ相当) 通路木を前スライドの方法でランダムに決める グループを前スライドの方法で決める 作物をDの昇順(D同じならSの昇順)にソートして おいて、空いているグループがあったら手前から 順に割り当てられる作物を割り当てていくだけ • 提出1.と比較して空きマスが長時間長い領域を 占める頻度がかなり減っている 8
グループ内利用率の改善 • 今はただソートした作物を順に割り当てているだけなので、 利用率が低い • 例えばD=10だけでグループを埋められるのに、D=9が残っていたから それも植えてしまうようなイメージ • 現在ターンsからあるターンdまでの計画を立て、 その利用率の大きさを評価指標とすることを考える • 計画の最終ターンdを固定するのがポイント • 計画を立てるべきターンよりも作物の Sを遅らせる・Dを早めることはできるが、 その分計画の利用率が小さくなる • できる限り早いS・遅いDを割り当てるのが得 • よって、 (Sを何ターン遅らせるか)+(Dを何ターン早めるか) の和が小さい順に貪欲に作物を割り当てるのが最適 • これは右図のような順序で作物を貪欲に割り当てるということ • これを計画の最終ターンdに関して全探索し、 一番利用率の高い計画を採用すればよい 9
提出3. 利用率を評価値とした割り当て • 開始11時間の提出 • 6時間くらい外にいたので実働5時間 • 150位相当くらい(38.7M、青パフォ相当) • アニメーションのリンク • https://twitter.com/Shun___PI/status/17019343289374028 24 • • • • 通路木を前スライドの方法でランダムに決める グループを前スライドの方法で決める 空いているグループに関して、作物計画を考える 計画を決める最後のターンdを全探索すると 前スライドの方法で最適な割り当てと 利用率が求まる • 提出2.と比較すると、利用率(緑の濃さ)の変化よ りむしろ通路マスの数が大きく減っている • 大きいグループへの割り当てでも利用率が下がりにくくなり、 通路マスが減るという形の恩恵 10
その他の貪欲の改善(未執筆) • グループの大きさに対して推定スコアを定めることで、 できる限りグループサイズが均一化(最大値を小さく) するように通路とグループ領域を焼く • 二段植え(長さAの作物の手前に長さBと長さC(A=B+C)の2つ の作物を植えるような計画を探索する) • 多段植え(=直線DP?) • 通路にも植えられそうなら植える 11
提出4. 通路+グループ方針の限界 • 終了1日半前、焼きなましを思いつく直前の乱択貪欲 (25位相当くらい、41.6M) • アニメーションのリンク • https://twitter.com/Shun___PI/status/17019345722405236 97 • 通路+グループ方針だと41M~42Mあたりで 限界が来る • 通路を最初に固定してしまっているため、それ以外を いくら頑張っても通路分の損失が大きい • 実際最終順位で20位辺りに赤い人とか AA社の人たちの壁があり、 このあたりが通路固定方針での限界に 近いのではと考えられる • DPで43M出しているすごい人もいる • 残り2日の時点で上位は44Mを出しており、 この時点で上位を狙うには何らかの新しい方針を 考えないとまずいと思っていた 12
上位解法のビジュアライザをイメージする • 上位の点数が明らかに異常なので、 仮に44Mの解法がある場合ビジュアライザは どんな感じか?を想像してみた • この時点で私の解法は41.5Mくらいなので、 2.5M=5%=50000点 の差がある • これは通路マス20マス分を丸々無くすことに等しい • (20マス÷400マス=5% なので) • ってことは右図のような感じ!? • そもそも通路を減らす分他のマスの利用率は 今の解法より低めになるはずなのだから、 通路をもっと減らさないと5%の差は埋まらない • 従って、おそらく上位解法に通路は存在しない! • ちなみに、こういう大方針に関する考察をする際は 制約の小さい盤面を考えた方がいいと思っている • 壁の多いseed0より壁の少ないseed2とかseed3を使って 考えた方が良い • 私はseed3どころか壁の一切ない盤面を想定していました 13
上位解法のビジュアライザをイメージする • 通路が存在しない=常に一面緑色の状態を想像し、 毎ターン収穫する作物を考えてみると、 以下のような考察が生えてくる 1. あるターンDに収穫する作物は、出入り口を含む 連結領域になっている必要がある • 当然遠くのマスにも届かないといけないので、連結領域は 細長い感じになっていそう 2. 常に一面緑色になっていないといけないのだから、 作物を収穫した場所は次のターンに即座に 何らかの作物を植えないといけない 3. あるターンDに近いターンにおいては、収穫領域 ができる限り被ってはいけない • 特に長さ1の作物が無い制約のため、ターンDとD+1で収穫 領域が被ると大きな損になる • ここまで考察すると、上位のビジュアライザは毎回 違うルートの収穫領域(雷・放電現象っぽい感じ) になっている脳内イメージが見えてくる 14
さらに考察を重ねる • 前スライド考察3から、ターン毎の収穫領域の 被りを少なくすることが重要なのだから、 特定ターンの領域のみを最適化するのではなく、 全ターンの領域を同時に最適化する必要がある 3, 8, 9, 15ターン目の収穫領域に 含まれるマスの場合 • あるマスについて各ターンの領域に 含まれる・含まれないが分かっているときに、 そのマスの作物計画が決定できるか考えてみる • これは「そのマスで作物を収穫or空けておくターン」が 決まっていることを意味する • 前スライド考察2より、作物を植えるターンは 「そのマスで作物を収穫or空けておくターン」の直後である • 従って、 「そのマスで作物を収穫or空けておくターン」で 時間を区切って区間の集まりとして考えると、 各区間に「作物をその区間の最初に植えて最後に収穫」or 「何も植えない」のいずれかを割り当てることで作物計画が定まる • まとめると、全ターンの領域を決めれば 全てのマスが時間軸に沿った区間に分けられ、 各区間に高々1つの作物を割り当てることができる ことが分かった • ここまで考察した時点で上位は全ターンの領域を 焼いているだろうという確信が8割くらい 黄緑は実際に作物が植わっている期間 濃緑は作物のS~Dの期間(スコアに寄与する期間) Dは必ず区間の右端に無いといけないが、 Sは必ずしも区間の左端でなくてよい 15
焼きなましの近傍操作を考察 • 全ターンの収穫領域を持って焼くのだから、 近傍は「あるターンの領域を(連結を保ったまま) 1マス増やす・1マス減らす」となりそう • この操作がどのように行えるか考える • あるマスに領域を増やすことは、そのマスの区間の1つに 区切りを入れることに等しい • 元々植わっていた作物を削除し、 区切った後の2つの区間に作物を割り当てるようにすれば、 状態やスコアを差分計算できそう • あるマスの領域を減らすことは、そのマスの区間の 区切りの1つを削除することに等しい • 区切りの両隣の区間に元々植わっていた作物を削除し、 区切りを消した後の1つの区間に作物を割り当てるようにすれば、 状態やスコアを差分計算できそう • (この差分計算だと作物の割り当てが全体最適と いう保証はできないが、ともかく)高速に近傍が 差分計算できそうなので かなり強い焼きなましができそう • ここまで考察した段階でかなり勝ちを確信しつつ 実装に入る 16
提出5. 焼きなましの実装 • 15位相当くらい(42.6M) • アニメーションのリンク • https://twitter.com/Shun___PI/status/170193487648703309 0 • 直前の41.6M貪欲を初期解として、とりあえず 焼きなましを実装した最初の提出 • 厳密には乱択ができなくなったのでもうちょっと貪欲の スコアは低い • 手元で25万ループくらい • まだ焼きなましが遅いので通路がかなり残っている ことが分かる(それでもスコアはかなり上がる) • ここからは焼きなましの高速化や効率化の改善を していくだけでスコアが爆発的に上がるようになり、 完全に別ゲーが始まった • ここからやっていることは問題固有のテクというより 汎用的な焼きなましのテクが多い • ので、ここからの解説はあまり役に立たないかも 17
提出6. 関節点高速化 • 10位相当くらい(43.4M) • アニメーションのリンク • https://twitter.com/Shun___PI/status/1701935066690261113 • 連結を維持したまま削除可能かどうかの判定に LowLinkを使った関節点判定をしていたが 遅くボトルネックだった • 自分の周囲3*3領域のみを用いた簡易関節点判定に より高速化 • 割とよく出てくるテク?(AHC019で初めて知りました…) • 3*3領域内で自分とつながっているマス(赤色)が、 自分を削除後も連結なら削除してよい • 手元で175万ループくらい • 通路がかなりぼやけた感じになってきた 18
提出7. いろいろ改善 • 最終日前夜時点、7位相当くらい(44.0M) • アニメーションのリンク • https://twitter.com/Shun___PI/status/1701935240464494792 • なんか乱数にrand()を使っていて遅かったので mt19937にした • 前スライドで述べたようにこの焼きなましにおいて 作物割り当ては全体最適でない • そこで、数千ループに1回全てのマスの全ての区間を 見直し、作物の再割り当てをすることで改善 • 手元で150万ループくらい • 今測ったら提出5.より回ってないんですが、あれ? 19
提出8. 最終提出 • 4位(44.5M) • アニメーションのリンク • https://twitter.com/Shun___PI/status/1701935464360640812 • 最終日の改善で最も効いたのは、 「領域の追加」操作の点数を高め、 「領域の削除」操作の点数を低くすること • こうすると、領域が常に最適な形よりちょっと大きな状態を 維持するので、関節点が減り削除操作がより活発に行われる • 乱数はmt19937でも重いので事前に1000万個作って それを使いまわした • 手元で500万ループくらい • 最終日は時間に追われ続けていたため何してたか あまりはっきり覚えていない… • 上位3人との差(敗因)としては、以下が考えられる • 関節点でも消せるようにすることで直線通路を曲げられる(消し た場合それを迂回するようにマスを追加すればよい) • 単純に高速化が足りなかった(1位は1500万ループらしい) • そもそも焼きなましに気づいた時点で残り1日半で あまり時間が無かった 20
まとめ • 焼きなましを思いついた瞬間を私のツイートでは 「天啓来た」と表現していたが、 実際は天啓というよりは、順位表の上位のスコアを起点に 論理の積み上げができたからこそ思いつけている、 ということが分かっていただけたのではと思う • アイデアを生み出すためにどのような過程を 経ているのか?を重点的に説明したつもりなので、 今後の参考にしていただければ • こういう思考はAHCだけでなくKaggleや業務でも役立つはず 21