AtCoder Heuristic Contest 028 ~Lucky Words~ 参加記

ALGO ARTIS プログラミングコンテスト2023 冬(AHC028)に参加して、93位/918人 でした。

解法や所感について書いていきます。

解法

大筋

一単語ずつ選んでいくビームサーチをしました。

遷移は、

  1. 暫定解の末尾との重複度合いの大きさ
  2. 現マスからの近さ

の順で単語に優先度を振って、その上位5個くらいを選んで打鍵、としました。

ノードの評価は通算コストにしました。

提出コード

Submission #49255662 - ALGO ARTIS Programming Contest 2023 Winter(AtCoder Heuristic Contest 028)

解法内でよく分からなかったところ

選んだ単語の打鍵の経路の出し方について、最適経路でない方が良いスコアが出ました。

最初はビームサーチで経路を求めていたのを、後からDP手法(厳密解を出せる(はず))に気付いて書き換える、という流れを経たんですが、DP手法にした方がスコアが悪くなってしまいました。
単純にDPの実装をミスっただけなのか、あるいは、ここの計算が適度に悪い方がビームサーチがむしろ良い塩梅に進む みたいな感じだったのかもしれません。

雑感

  • 「貪欲法をした後、それを使ってビームサーチをする」というテンプレパターンをやった回だった。
  • 最近では珍しく(?)シンプルに考察できる回だった気がする*1。順位とレートの相関の高い回だったんじゃないかなという気がする(確認はしてないですが)。
  • コンテスト後に考えてみたけど、このビームサーチだと利益の先食いみたいなことが起きるだけであんまり嬉しく無さそうな気がした。打鍵コストの低い単語が序盤に当てがわれるだけで、全体を通した効率改善みたいなものに利するわけでは無いように感じる。TLの雰囲気からも感じたが、やっぱりローカルサーチ回だったのだろうか。

*1:ビームサーチや焼きなましに落としやすいというか

AHC022復習 ~「入口と出口の対応確率テーブルを作る」手法を試して、8位相当のスコアを出すまでにやったこと~

今まで統計・推定系の知識がほとんど無いままマラソン系のコンテストを戦って来たのですが、ACH022を経てさすがにこのままじゃ不味いなって気持ち*1になりました。

しかし、公式の解説動画を見ても(私の前提知識が足りなさすぎて)いまいち理解できず、どうしたものかなと思っていたところで、bowwowforeachさんの推定部分を重点的に解説してくれている記事を見つけました。 bowwowforeach.hatenablog.com

こちらがとても分かりやすく、この記事と合わせて改めて公式解説動画を見直したらそちらもある程度ピンと来ました。
おかげで実装できそうな気持ちになったので実際にやってみたら、ありがたいことにコンテスト時の8位相当のスコア*2を出すことができました。

ただ正直、上記2つの資料に沿って実装しただけで、かつ資料の出来もそもそも良いので、正直改めて書く内容も取り立ててはないんですが、
一応実装を通して得た、

  • どの程度のことをするとどのくらいの順位になるかの情報
  • もうちょっと具体的に、どう実装するかみたいな話
  • 配置や計測の回し方の例

といった、本筋である推定の理論・説明以外のところの情報は提示することができそうなので、
補足的な立ち位置の資料として、同じように勉強する人の助けにならないかな、みたいな期待からその辺りを記述した今回の記事を作りました*3

やったことと、その順位

やったことの内、ある程度スコア*4が向上した内容を節単位で列挙しています。
節タイトルは やったことの概要 (暫定テスト順位) って感じで振ってます。

以降の記述は、前述したbowwowforeachさんの解説記事(以降は「参考記事」と呼びます)の内容を把握していることが前提になります。

参考記事に書かれていることを素直にやる。配置や計測順序は割と適当。 (42位)

参考記事にある、「累積分布関数を使って入口出口の接続確率テーブルを作る」を素直にやると、配置や計測順序で大した工夫をせずともこの順位が取れました。(凄い)

以降は、配置と計測を具体的にどうしたかを書いていきます。

配置

公式解説動画にも出てきた、一点高温を作ってそこを頂点の山にする配置をしました。
以下の画像のような感じになります。

もうちょっと具体的には、
頂点の温度を1,000、その四方を500とし、以降は頂点からのマンハッタン距離が1離れるごとに 1000/L ずつ温度を下げていきました。

山の頂点の座標は、各出口からのマンハッタン距離の総和が一番小さいセルを選んでいます*5

Sに応じたチューニングとかは特に無いです。

計測の回し方

入口をID順に計測対象にしながら*6、以下の手順を繰り返します。全ての行で最大値が閾値を超えたら終了します。

  1. 入口出口の接続確率テーブル(以降は「確率テーブル」と呼びます)の、対象入口の行の最大値が閾値を超えていない場合、2以降を行う。
  2. 対象入口行の最大値の出口の座標から山の頂点に向けて計測を実行し、参考記事のやり方に沿って確率テーブルを更新する
  3. 確率テーブルの横方向の正規化を、全ての行に対して行う

閾値は98%にしました(seed 0~99で試して、誤回答が出なかったのがこの辺りのラインでした)。

縦方向の正規化 (24位)

公式解説動画*7や参考記事の最後の節に書かれていた内容になります。
実装が楽そうだったのでとりあえずやってみたら、かなりスコアが伸びました。

具体的には、計測ごとに以下を実施しました。

  1. 横方向の正規化を全行に
  2. 縦方向の正規化を全列に
  3. 横方向の正規化を全行に

(正規化の掛け方として適切なのかは不明です。)

閾値の変更 (20位)

42位解の計測の周し方節内で出てくる閾値の値を98%から95%に下げました。

縦方向の正規化を入れたお陰なのか、閾値を下げても誤回答が出なくなったので*8、計測コストを下げるためにこうしました。

計測序盤は山の頂点に近い出口を優先して扱う (17位)

公式解説動画で出てきた、近い出口から扱う手法*9を部分的に導入しました。

具体的には、

  1. 頂点からのマンハッタン距離が10以下の出口を列挙する。
  2. 手順1で列挙した出口に対して、距離が小さい順に手順3を実行する
  3. 対象の出口の確率テーブル列の最大値が95%を超えるまで、「その列の最大尤度の入口を計測対象として、出口座標から山の頂点を指す計測」を繰り返す。
    • この際正規化は、24位解の手順から3を省いたものを行う*10

上記の手順を終えた後は、前節までと同様の手順で計測を進めて完了です。

情報が少ない序盤ほど、確定までに計測回数がかかるはずなので、序盤に距離の短い計測が集まるようにこうしました。

元々は、「距離が10以下の出口」ではなく「全ての出口」を対象にするつもりだったんですが、やってみたところ期待通りにはいかず23位に落ちてしまいました*11

2024/03/14 追記

実装をミスっていて、対象の出口をきちんと列挙できていないバグが埋まっていたことに気づきました。
8位解に対してここの修正を入れたら、6位に上がりました。確認はしていないですが、この時点でも17位より良い順位になるかもしれません。

Sが小さいケースで山を緩やかにする (8位)

ここまで配置の仕方は42位解のもののままで固定していましたが、
手元の試行結果を見ると、Sが小さいケースで配置コストがネックになっていたので、計測容易さよりも配置コストの低さを優先するようにしました。

大筋の方針は節タイトルの通り、Sが小さいケースで山を緩やかにする感じです。
具体的には、42位解の配置をベースに、Sの大きさごとに以下の変更を加えました。

  • S <= 36
    • 山の頂点の高さを600に下げる
    • 傾斜の温度を1000/Lずつ下げていたところを、500/Lずつに変更*12
  • 49 <= S <= 100
    • 山の頂点の高さを700に下げる
    • 傾斜は1000/Lのまま変更なし
  • S >= 121
    • 変更なし

S<=36のケースの配置例

これで8位スコアを取ることができました*13

もう少し調整は頑張れる気もしましたが、一桁順位を取れて満足したのと、統計・推定系の知識を学ぶっていう復習の趣旨から外れつつあったので、ここで復習を終えることにしました。

終わりに

率直にやるだけでも42位が取れてびっくりしました。やっぱり知識って大事だな、とか数学の力って凄いな、とかを思いました*14
コンテスト時に必死こいて作った自作解法はなんだったのだろう、みたいな気持ちにもちょっとなりました*15

統計・推定の知見が元々ほとんどなかったのですが、分かりやすい資料の存在のおかげで無事に実装することができました。本当にありがたいです。

今回の取り組みを通して、推定系問題への苦手意識もある程度取り除けた気がします。
次に推定系の問題が出たときは良い順位を取りたいな。

*1:レートの伸び悩みだったり、コンテスト後Twitterタイムラインの理解できなさだったり。思ってたより統計・推定の知識が求められる問題の出題頻度高いな、とかも思いました。

*2:暫定テストで

*3:あとは単純に、良いスコアが出たのが嬉しくて何か書きたくなったというのもあります。

*4:暫定テスト順位を基準に見てます

*5:計測コスト削減のため

*6:ID末尾を迎えたら先頭に戻ってループする

*7:51:40~ あたり

*8:100ケースしか試していないので、たまたまそうだっただけかもしれないです。

*9:37:20~ あたり

*10:横->縦->横 でなく、横->縦 で終える。

*11:手元で見た感じ、Sが大きいケースで計測コストが上がっている傾向があるように見えました(100ケースしか試してないですが)。

*12:緩やかかつ範囲を広くするイメージ。

*13:暫定テストのスコアを指標にチューニングした感はあるので、本番テストの順位はこれより低い気がします。

*14:小並感

*15:ちなみにコンテスト時の暫定テスト順位は134位でした。

AtCoder Heuristic Contest 022 ~Exploring Another Space~ 参加記

RECRUIT 日本橋ハーフマラソン 2023夏(AHC022)に参加して、146位/1,070人 でした。

解法や所感について書いていきます。

解法

大筋

上図のように盤面を均等に4分割し、温度は2値だけ使って、右下のエリアの温度だけを高くした配置をしました。
こうすることで、x軸y軸が0の部分の切れ目までの距離を二分探索っぽく特定できるようにして、計測回数をある程度少なく抑えるようにしました。

次の節からもう少し詳しく書いていきます。

配置

盤面を均等に4分割し、右下のエリアの温度だけを高くした配置をします。
奇数長盤面の場合は、赤側の一辺の長さを l/2 + 1 としています。

温度差は S の大きさと見合わせながら決めています。後ほどもう少し詳しく記載します。

推定手順

軸それぞれで独立に、0までの距離を求める方針になります。

最初に移動無しで計測して、出口位置が赤か白かを特定します。

出口位置が赤だった場合の、x=0までの距離を求める手順が以下になります。

  1. (0, -l/2) *1を計測する。基本的には白になるので、その場合2に進みます。特殊ケースである赤の場合の対応は右の注釈に書きました*2
  2. (-l, -l/2]区間のどこかが x=0 であることが確定しており、かつ単調非増加であるので、二分探索の要領で x=0 の位置を割り出す。

y軸についても同様の手順を行えば、出口の座標が割り出せます。

初期位置が白だった場合は、まず (+l/2, 0)(0, +l/2) の計測結果を見ることで、出口がx軸y軸それぞれで l/2 以下の位置にあるかどうかを確認します。
l/2 以下に居ないなら上記の手順1から、居るなら手順2から操作を行い、座標を特定する。その際、探索範囲に赤エリアを含めるために、確認対象でない方の軸を l/2 ずらす必要があります。

温度差と計測確度の設定

信頼区間4.1σにして、計測が間に合う範囲でできるだけ白と赤の温度差を小さくしました。
配置コストが計測コストを下回れる場合は、配置コストと計測コストが近い値になるラインを狙って設定しました。
逆に白をP=0、赤をP=1,000としても計測が間に合わない場合は、信頼区間を最悪3.8σまで落としました。

精度はどうか

実測して見てみました。seed 0~199 の実行結果を確認したところ、

  • 誤回答数0が175ケース
  • 最大誤回答数が3で2ケース

といった感じでした*3
概ね正解できるし、外してもまともに点数が取れるラインには収まる、くらいな感じといったところでしょうか。

その他工夫

  • 計測回数を抑えるために、計測で端値*4を引いたら標準偏差分はみ出るように値を加えた*5
  • 二分探索チックな操作をするパートで、絞り込んだ範囲に出口が1つしかない場合は、その時点で確定して計測を打ち切った。

提出コード

Submission #44799051 - RECRUIT Nihonbashi Half Marathon 2023 Summer(AtCoder Heuristic Contest 022)

感想

期間後半くらいに入るまで、Sが大きいケースで1点しか取れない状態が続いていて、中々苦しいコンテストでした。一方である程度点数が取れるようになってからは、追加で色々と試したいことが思い浮かんで、かなり楽しめました。

知見のほとんど無い推論系問題*6でこの順位取れたのはまあ頑張った方ではないかな、と思っている一方で、そんなこと言ってないでちゃんと推論系の知識身につけないと不味いよなって気持ちになりました。コンテスト後のTLを眺めていても、知識不足で何を言っているのか分からないことが多かったですし。。 インタラクティブ問題は概ね推論とセットみたいなところがあるはずなので、AHCを戦っていくなら避けられないだろうなとかを思っています。

とりあえず上位の解法を見つつ、今回の復習を頑張ろうと思います。

追記

2023/09/03
統計・推定解法について扱った復習記事を書きました。

gobi-tk.hatenablog.com

*1:問題文に沿って、計測座標は (Y, X) で表記

*2:盤面の長さが奇数かつ出口点の配置が右端か下端の場合のみ、赤になります。この場合は、その時点で -(l-1) が軸0で確定します。

*3:記載した解法以外の細かい工夫も入ってるので、参考値です。

*4:0か1,000

*5:例えば標準偏差400で、Pの2値を200と800に設定していた場合に1,000を引いたら、( 1,000+(400-200) ) の1,200が計測値として得られた扱いとした。

*6:保有知識の内で今回使えたのは、正規分布区間推定の信頼区間の式くらいでした..

簡易にビジュアライザを作る方法の調査記

ラソンコンテスト終了後のツイッターTLとかで、他参加者の自作ビジュアライザを見るたびに「次回は自分もやってみよう」と思うものの、どう手をつけたものかなとなって結局作らずに終わる、
みたいなことが繰り返されていたので対策として、敷居を下げるような手軽に作れる方法が何かないかと調べてみました。

その結果、2つほど良さそうなものを見つけたので、備忘録としてこの記事を作りました。
同じ悩みを抱えている人の参考にもなったら幸いです。

実際に自作もしてみていて、その際 AHC018 を題材にしています。
問題内容を把握している方が読みやすいとは思いますが、例示に使うだけなので、そうでなくてもさほど支障なく読めると思います。

概要

  • 「公式Webビジュアライザを流用する方法」
  • 「コンソールでビジュアライズする方法」

の2種類を紹介します。

それぞれ以下のような利点があると感じて選定しました。
「公式Webビジュアライザを流用する方法」は、タイトルの通りに公式Webビジュアライザの機能を流用できるので、自前で用意しなければならないものが少なくて済みます。例えば、入力フォームやイベントハンドラ、アニメーション機能が公式Webビジュアライザの方で用意されています。
「コンソールでビジュアライズする方法」は、コンソール上で完結しているため、HTML・CSSCanvasといった GUI用の知識を新たに入れる必要が無く、コンテストで使う知識の延長だけで作れる点が利点だと感じています。

ここからは、それぞれの手法について詳しく見ていきます。

公式Webビジュアライザを流用する手法

tsukammoさんのAHC018参加記内で使用されていた手法です。
hackmd.io


真似て自作してみたものが、以下の画像になります。

元の公式Webビジュアライザがこちらで、これをダウンロードして改造して、本体の右横に白黒の図を付け足した形になります。
本体画像の補助情報として、掘削の深さをグレースケールで表示しています。

アニメーション機能にも乗っかっていて、以下のように動画表示もできます。


この手法ではこのように、本体のビジュアライザを補強する形で情報を載せることができます。
この例では図形でやっていますが、そのターンの補助情報をテキストで表示する、くらいな使い方でも十分有用なのではないかなと思っています。

続けて、実装周りの話をしていきます。

ローカル起動手順

以下の手順で、公式Webビジュアライザをローカルで実行できます。

  1. 公式Webビジュアライザをダウンロード保存
  2. 1.のHTMLから依存しているjs ファイルとwasmファイルをダウンロードして同一ディレクトリに突っ込む
  3. 上述のディレクトリ内で http-server を実行して、ローカルHTTPサーバを立ち上げる
  4. http://localhost:8080/Visualizer.html にブラウザからアクセス

(ローカルファイルホスティングをしているのは、CORS 回避の都合です*1。)

ビジュアライザ改造の実装の説明

Canvas APIを主に使用して実装しています。

公式WebビジュアライザをダウンロードしたHTMLファイル(以降はVisualizer.htmlと呼びます)に以下のタグを追記して canvas を設置。
<canvas id="canvas" width="400" height="400"></canvas>

下記のJSファイルをVisualizer.htmlに読み込ませる。

const N = 200
const MAX_D = 5000

var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d')

function callMyVis() {
    context.clearRect(0, 0, canvas.width, canvas.height)
    const turn = document.getElementById('turn').value
    const output = document.getElementById('output').value
    const rows = output
        .split('\n')
        .filter((row) => row[0] != '#')
        .map((row) => row.split(' ').map((sn) => Number(sn)))

    var tbl = new Array(N)
    for (let y = 0; y < N; y++) {
        tbl[y] = new Array(N).fill(0)
    }

    for (const [y, x, power] of rows.slice(0, turn)) {
        tbl[y][x] += power
    }
    for (let y = 0; y < N; y++) {
        for (let x = 0; x < N; x++) {
            const power = tbl[y][x]
            if (power == 0) {
                continue
            }

            context.fillStyle = grayScale(255 * ((1 - power / MAX_D) * 0.9))
            context.fillRect(x * 2, y * 2, 2, 2)
        }
    }
}

function grayScale(n) {
    return `rgb(${n}, ${n}, ${n})`
}

(JSコードは、ビジュアライザが指定しているターンの掘削状況を作成しキャンバスに描画、をしています。)

そして、Visualizer.html内にあるvisualize()関数の冒頭で、上記の callMyVis() 関数を呼べば完成です。

visualize() 関数はアニメーションターンが更新されるたびに実行されるので、これだけでアニメーション化できちゃいます。

気になっている点

本体のビジュアライズ画像自体の改造もしたかったのですが、参考記事内でも書かれているように、その部分はWebアセンブリで生成されていていじれませんでした。
(というよりその都合で、本体のビジュアライズ画像の補足情報を足す、という構成にしています。)

一方で、ローカル版ツール内の vis.rs がWebアセンブリを吐くように作られているように見えるので、これを上手く組み込めるならなんとかできるのかもな、とかを思っていますが確かめてはいないです。

関連する良さそうな手法

iwashi31.hatenablog.com

iwashi31さんがAHC019後に出していた上記の記事で、ローカルに落としたWebビジュアライザを改造して、実行結果を自動で読み込むようにする手法 が紹介されていました。

問題に依存せず使えそうでかつ有用そうで、良さそうな手に感じました。
今後のコンテストでテンプレとして使っていこうと考えています。

コンソールでビジュアライズする方法

mooakiさんのこちらの記事を参考にしました。

mooaki.hatenablog.com


AHC018を題材に自作してみたものが以下になります。コンソール上で実行されてます。


こんな感じで、コンソール上でも思いの外*2リッチなアニメーションが作れます。
参考記事内の15パズルのビジュアライズも凄いので是非見てみてもらいたいです。

これは主にANSIエスケープシーケンスという、ターミナル出力の色付けや削除、カーソルの移動などを表現できる端末制御用のシグナル*3を使用して作成しています。
新たに覚えなければいけないのはこれくらいで、基本的には文字列を組み立てて出力するプログラムを作る、という作業になるので、使用知識はコンテストで使う範囲で概ね事足り、GUI向けの知識を新たに仕入れることなく制作できるという点がメリットとして大きいと感じています。
シーケンス自体もそんなに覚えることもなく、参考記事中の「プログラム中でよく使いそうなものは関数にしておくと便利です。」と書かれている箇所の下に例示されてる内容くらいで概ね事足りると思います。

あとは、コンソールに閉じて作業できる*4のも利点になりそうかなと思っています。

詳しい作り方は参考記事の方に書かれているので、それはそちらを読んでいただくことにして、 ここでは自分がやってみた上での所感やtipsを書こうと思います。

以降は、参考記事の内容を把握していることが前提の内容になります。

自作ビジュアライザの軽い説明

最初に盤面を記述し、その後ターンごとに掘削箇所にカーソル移動して、グレースケールで表現した掘削量を上書きする、ということをしています。
盤面の各セルは、空白文字に背景色を付与する形で表現しています。

これ自体に公式ビジュアライザ以上の表現力は無いのですが、今回は手法を試すこと自体が目的だったのでこれでよしとしています。

気になったところ

  • AHC018の盤面サイズだと、差分更新にしないと速度的に少しキツかった*5
    表示スペースサイズ的にもちょっと辛さを感じたので、参考記事で扱っているAHC011のような、盤面サイズ小さめの問題向けの手法な印象ではありました。
  • 文字が縦長なので、1セル1文字で作ると画面が縦長になってしまう。
    • コンソールの文字フォーマットの設定でなんとかできそうな気もします。今回の自作例では、気にせずに縦長盤面で妥協しちゃってますが。

tips

背景色のカラーコードの取り扱い

自分の環境では、こちらに載っている「RGBでのカラーコードを指定」が背景色に対しては使えなかったので、「0~255のカラーコードを指定」する方法を利用しました。

これにあたってカラーコードの選定に悩んだので、
以下のような、カラーコードの番号とその色を表示するコードを用意しました。

for i in `seq 0 255`
do
    printf "\e[48;5;%dm%d" $i $i
    printf "\e[0m "
done;  printf "\e[0K\n"

以下のような出力が得られます。


232~255 の連番のところがグレースケールになっているので、濃淡を表現したいところでの使い勝手が良さそうでした。実際、掘削の深さはこれで表現しています。

224,210,209,197,160 あたりで赤色の濃淡の表現もできました*6。岩盤の硬さはこれで表現しています。

終わりに

やはりというか、視覚的に結果を確認できるものができると達成感を得やすくて良いですね。眺めてニヤニヤしています。

私は元々、画像をパラパラ漫画的に表示するだけのビジュアライザ、くらいしか作ったことが無くて、ビジュアライザ作りに対しては苦手意識を持っていたのですが、今回調査した手法のおかげである程度のもの*7を作り切ることができました。
私と同じように苦手意識を持っている方は是非試してみて欲しいです。

*1:元記事ではCORS許可する形を取っていますが、若干の怖さを感じたのでここでは自前でホスティングする方法を取りました。

*2:かどうかは人によるでしょうが、自分はとても驚きました。

*3:で言い方が合っているのか分からないですが

*4:ブラウザへの移動とかをしなくて済む

*5:動くけどちょっと遅いなと感じるくらい。

*6:色の変化量が均等になっては無いと思いますが

*7:のつもり

AtCoder Heuristic Contest 019 ~Silhouette Block Puzzle Creation~ 参加記

MC Digital プログラミングコンテスト2023(AHC019)に参加して、
535,142,906,156点の 84位/786人 でした。

解法や所感について書いていきます。

解法

以下のような、2段階の乱択を行いました。

  1. 「各シルエット組から座標をランダムに1つずつ選び、BFSで同型を保ったまま出来るだけ大きく膨らませる(回転24パターン全て試す)、という操作を影を埋め切るまで繰り返す。」という乱択を5.0秒実施。
  2. 1.のベスト解に対して、後半1~7手を壊して 1. と同様の乱択で再構築する作業を0.7秒実施。

それぞれのパートについて、もう少し詳しく説明していきます。

1.の乱択生成パートの詳細

両シルエット組について、正面・右側両方の影に含まれる座標(以降、「正当な座標」と呼びます。)の内、新規に1つ以上影を作れるものの中からランダムに座標を1つずつ選ぶ*1
選んだ座標を種に、隣接6方向を遷移候補とするBFSを行い、シルエット組両方が正当かつ未使用*2である場合のみ遷移を許す形で、できるだけ大きく膨らませる。この操作を24回転全パターンについて試し、最も体積が大きかったものをブロックとして採用する。
この操作を、シルエットが埋まるまで繰り返すことでブロックのセットを生成する。

上記の処理を5.0秒経過するまで繰り返し実行し、スコアが最善だったものを 2 の処理に引き渡します。

また、ここに多少ヒューリスティックな工夫を加えていて、
1x1で突起していて、かつ影を作る上で必須である点は先んじて潰すようにしています。
具体的には、

  • 正面・右側いずれかの影で、同一 z 座標上に自身以外の点がない*3
  • 隣接6マスの正当な座標が 0~1個

を満たす点を乱択の最初に優先して選ぶようにしています。
扱いにくい点(のはず)なので、それ同士で潰し合わせるなり、膨らませる起点に使うことで、後半に余らせないようにしたほうが良さそうだな、という判断の元で実施した手でした。
スコア的には、これで提出絶対スコアが 87G から 84G へと伸びています。

2.の後半手を壊して乱択再生成するパートの詳細

1.で生成したベスト解の最後から1~7手分を壊して*41.と同様の手法で乱択再構築する。
この処理を0.7秒実行し、スコアが最善のものを解答として選びます。

1. だけで時間一杯回しても、後半はほぼ更新されなくなってしまっていたので、その分をベスト解の改善に注力する意図でこれをしました。
序盤の置き方を変えると、影響が大きすぎて部分破壊の体を成さないのでは無いかなと思って、後半手を壊す形にしましたが、それで良かったのかどうかはあんまり分かっていないです。

スコア的には、これを入れたことで提出絶対スコアが 110G から 87G へと伸びています。

テストケース0001のビジュアライズ結果

提出コードはこちらです。
Submission #40299991 - MC Digital Programming Contest 2023(AtCoder Heuristic Contest 019)

解法に対する課題感

  • ループ回数不足
    • 大きいケースだと千回も回っていなかった。
  • ローカルサーチ化したかったけどできなかった。近傍が思い付かず。
  • ブロックの総体積と個数が一緒なら、大きさは均等にした方が有利なのだから、膨らませるだけ膨らませる はあまり上手くなかったかもしれない。
    • コンテスト後TLで、置く場所を先に決め切って、それから全体的にバランス良く膨らませる、みたいな手法を見て良さそうだなと思った。
  • 体積だけで無く、置く難易度も評価に組み入れるべきだった気がする。
    • 隅を埋めたら高評価、みたいな工夫をしたかった。
  • 解法手順の 2.で表現したかった気持ちは、MCTSが近かったかもなとコンテスト後のTLを見て思った*5

コンテスト終了後の雑感

  • 実装がとにかく重かった。今回もコンテスト期間が足りなかったなという気持ちに*6
    • 解法の 1.の手法ができたのが最終日前日の半ばとかだったので、そこから伸ばすための試行錯誤の時間がほとんど確保できなかったのが辛かった*7
  • 公式配布ツール内の、is_same 関数をメチャクチャ参考にした。というか3次元座標の扱い周りの知識が元々ほぼほぼなかったので、これがなかったらほとんど何もできなかった気すらする。
  • 中盤くらいまで、最終形がどうなってるのかが想像つかなすぎて、何を優先して用意するべきかが分からず動きづらかった。

終わりに

最初問題を開いた時の印象は、「うっ3Dか、嫌だな」だったんですが、
やってみると思っていたよりずっと扱いやすく作られていて、終始のめり込んで楽しんで取り組めていました。
AHCラジオ内で、3Dビジュアライザは最近できた 的な話があった気がする*8ので、これから3D問題がぼちぼち出てくる感じになるんですかね。

私事ですが、今回で黄色への昇格を達成することができました!とても嬉しい!
これからも精進していこうと思います。次は総合順位100位以内を目標にしようかなと思っています。頑張るぞい。

*1:影を作れる座標が無い場合は、正当な座標から1つ選ぶ

*2:ブロックが設置されていない を指す

*3:必須で置く必要がある、を意味します(はず)

*4:手数はランダムに決めます。

*5:思いついても実装しきれていたかは怪しいですが。

*6:毎回言っている気がしますが。

*7:実装だけでなく考察の遅さのせいもあるけど

*8:聞き間違ってないかあんまり自信ないですが

AtCoder Heuristic Contest 018 ~Excavation~ 参加記

RECRUIT 日本橋ハーフマラソン 2023冬(AHC018)に参加して、
1,359,993,445,143点の 229位/1,082人 でした。

解法や所感について書いていきます。

解法

大筋の流れ

クラスカル法の要領で接続する2点の候補を決める。
実際に接続する前に掘削コスト調査を挟み、良さそうな経路があったら接続を実行、無かったら次善次々善の候補の調査へと進む。
という感じでした。

もう少し具体的には、下記の手順を取っています。

  1. 水源・家の中からペアを作って、マンハッタン距離をコストとする形で優先度付きキューに突っ込む。
  2. コストの小さい順にペアを取り出す。以下の条件を満たす場合は3に進む。そうでなければ2を繰り返す。
    • ペアの中に、水源と接続していないものがある
    • ペアの2点を対角点とする長方形の中に、他の家や水源は含まれていない
  3. 掘削コストの低そうな経路を調査して、有ったら実際に繋ぐ(調査の方法は次節に記載)。
    無かったら接続はせずに、その候補を優先度付きキューの最後に回して*1 2 に戻る。
  4. 接続経路の中で、全水源・家に対してそれぞれ最短の点を調べ、そのペアを優先度付きキューに加える
  5. ゲーム終了していなければ、2 に戻る

掘削コストの低そうな経路の調査方法

前節の 3 でやっている、2点間の接続経路の調査方法。

  1. 2点を対角点とする長方形内で、xy座標両方が11の倍数である点を調査点とする*2
  2. 調査点を所定の深さまで掘る*3
  3. 岩盤破壊できた調査点を対象に、縦横の接続だけで始点から終点まで接続できるなら、その経路を採用して終了
  4. 掘る深さを増やす
  5. 掘る深さが調査上限に達したら、接続に向かないと判断して調査打ち切り。そうでなければ、2に戻る。

パラメータチューニング

ローカルで1,000ケース周して絶対スコアの合算値を見る形で、手動チューニングを行った。
幸い(?)今回は実行時間を使うような解法ではなかったので、1回周すのに20秒くらいで済んだ。

チューニング対象は、以下。

  • 掘削処理の1回あたりのパワーの大きさ
    • C ごとに設定した
  • 調査点の間隔
  • 2点間の接続調査を打ち切る深さ の上げ幅*4

パラメータチューニングが順位の伸び的には一番効いた。

課題を感じたところ

  • 最短経路しか調査していないため、迂回するのが有力なケースに弱い
  • 調査点の目が荒くて、岩盤が厚いところを通ってしまっている
    • チューニング結果が良かったのがこれではあったが
  • パラメータチューニングが手動
    • Optuna 使いたかった

他にも細かいやりたいことは山ほどあったが、コンテスト時間が全然足りませんでした。。

テストケース0000のビジュアライズ結果

提出コードはこちらです。
Submission #39277152 - RECRUIT Nihonbashi Half Marathon 2023 Winter(AtCoder Heuristic Contest 018)

コンテスト終了後の所感

TLを見て活かせそうだと思ったこと

  • 接続調査のところはA*を使うのが良さそう
  • C に応じて調査点の間隔を決めた方が良さそう
  • ローカル実行時、絶対スコアを見てたけど上手くなさそう。頑張って相対スコアの近似を作るべき。

AHCラジオを見て

AHCラジオ: RECRUIT 日本橋ハーフマラソン 2023冬(AHC018) - YouTube

  • 入力値についての出題者意図とかが聞けて面白かった
  • 水源同志は連結している扱いにしておくと実装が楽って話を聞いて、なるほどと思った

その他雑感

  • 実行時間をめちゃくちゃ余らせたけど、どう使うのが良かったのだろうか。
  • パラメータチューニングが滅茶苦茶効いた。珍しい問題だなと思った。
    • 掘削1回あたりのパワーをいじってから一気に順位が上がった
  • 終盤までスコアが全然伸びなくてとてもつらさを感じた。最後の3時間で300位くらい伸ばした。
  • 最初に問題文読んだ時は「この情報量じゃ運ゲーにならないか?」と思った。実際は全然そんなこと無かったですが。
  • 数日経つまでサンプルコードがあるのに気づかなくて、みんな初動強いなと焦っていた
    • サンプルコードがまずまず強いのも悪い(適当)
  • 盤面生成の Perlin noise のあたりの話をどう解釈すべきかで困惑した。調べてみて、なだらかに変化するってところの情報が肝で、パラメータ推論(?)的なことは求められてないんだろうな という判断というか決め付けというかをして進んだ。
    • AHCラジオを聞いた感じだと、出題者意図的にはそれで良さそうに聞こえた

*1:コストを一定量加算して優先度付きキューに突っ込み直す。キューが一巡したら、許容する掘削コストを上げる。

*2:こうすることで、別調査と調査範囲が被ったときに、調査点を使い回せる

*3:ただし、辿り着けないことが分かっている調査点は対象から外す

*4:調査方法の5の打ち切り判断に使う

AtCoder Heuristic Contest 015 ~Halloween Candy~ でやったこと

トヨタ自動車 プログラミングコンテスト2022(AHC015)に参加して、
142,839,445点の 17位/778人 でした。

考察内容や解法について書いていきます。

考察と方針

パズルゲームの2048をベースにした問題でした(のはず)。
操作方法とブロック*1の出現の挙動は一緒だが、ブロックの融合が無しでゲーム目標も違うといった感じでした。

昔に会社でのお遊びで2048のソルバーを書いたことがあって、その時の所感が活かせないかをひとまず考えました。
本コンテストとルールが共通している箇所で、関係がありそうな要素として具体的には以下がありました。

  1. ルールベースで十分クリアできる
  2. 四隅のどこかを決めて、そこでブロックを育てるのが良い*2
  3. ランダム要素のせいで(?)探索の工夫はあまり上手く決まらない
  4. 一操作について考えるとき、移動前よりも移動後に出現するブロックの方が、それに対する影響が大きい*3

良い感じのルールベースがこの問題でも作れるかは懸念だったのですが、Webビジュアライザのマニュアルモードで遊んでみたところ、
2 の応用で「端に特定のキャンディを集める」はある程度できそうに感じました。

3 に書いたように、2048では探索はいまいちどう工夫したら良いのか分からなかったので、
本問題でもここは割り切って工夫せず、間に合う範囲で単に全探索をする、と決めました。

以上を踏まえて、「基本的にルールベースで進め、残ターン数が減って全探索できるようになったら全探索に移行する。」という方針を取りました。

解法

94ターン目までルールベースで進めて、最後6ターンだけ全探索。

ルールベース部分

一番個数が多いキャンディをA、他2つをB,Cとする。
Aが右側全般、Bが左上、Cが左下に寄るように方向づけるイメージで操作する。ただし、Aを右に寄せるのを最優先する。

具体的には、Aが次に出現するときは 'L' をし、出現ブロックが既存のブロックの右側に配置されるように誘導する。
Aの出現が切れたタイミングで 'R' をし、他のブロックが既存のブロックの右側に現れないようにする。

また、'L''R' の内、最後に使われたものが 'R' である場合、Aの配置への悪影響が少ないため上下操作を許して、B,Cの配置誘導を目指す。
具体的には、次にBが来るときは 'B' 、C が来るときは 'F' をする。

全探索部分

ゲーム終了まで、MiniMax的なゲーム木探索をする。

遷移は、傾け操作4種と、キャンディの出現全パターン が交互に行われる。
終了盤面でスコアを算出し、傾け操作の遷移を辿るときは子ノードの最大値を、キャンディの出現の遷移を辿るときは子ノードの平均値を、親ノードのスコアとした。

テストケース0000のビジュアライズ結果

イチゴが一番多いケースで、イチゴを右に、カボチャを左上に、メロンを左下に集めています。

提出コードはこちらです。
Submission #36094840 - TOYOTA MOTOR CORPORATION Programming Contest 2022(AtCoder Heuristic Contest 015)

感想

過去最高順位が取れて、とても満足でした。
2048パズルの知見があったのがまずまず有利に働いたように感じます。

もう少しで黄コーダーにいけそうなので、もう一押し頑張っていこうと思います。

*1:キャンディに相当

*2:e. g. 右上を選んだなら、L, D の操作は極力しない。

*3:chokudaiさんのツイートに良い感じの説明がありました https://twitter.com/chokudai/status/1586668572457082880