平方数の末尾に同じ数字はいくつ続く?
ストーリー
ねこ:「 は末尾に同じ数字
が
個並んでるね。」
きり:「平方数で、末尾にもっとたくさん同じ数字が並ぶものあるかな?」
ねこ:「末尾が のものは
のようにいくらでも長くできるね。」
きり:「 以外だとどうだろう?無限に長くできる?それとも最大値がある?」
ねこ:「気になるね。」
きり:「 進法以外で考えてみても面白そうだね。」
ねこ:「だね。やってみよう。」
なおこの記事はえびまさんの動画を参考に、議論を一般化したものです。
平方数の末尾に4は何個まで続くか?という1分の動画を作りました。 pic.twitter.com/MIkM9Tb8yw
— えびま (@evima0) 2025年9月4日
10 進法の場合
準備
を
で割った余りを
と書くよ。
また平方数 を
や
で割った余りの制約は以下のとおりです *1 。
これを使って、 進法で末尾に同じ数字がいくつまで連続して現れるか調べよう。
平方数の末尾に現れない数字
で割った余りに注目すると、平方数の下
桁が
になることはないことが分かる。
平方数の末尾に1つまで連続して現れる数字
および
より、
は平方数の末尾に高々
つしか連続して現れないことが分かる。なお、百の位以上は、
で割った余りに影響しないことに注意してね。
つ現れる例は
があるよ。
平方数の末尾に3つまで連続して現れる数字
つまり
より
は平方数の末尾に高々
つしか連続して現れないことが分かる。なお、一万の位以上は、
で割った余りに影響しないことに注意してね。
つ現れる例は
があるよ。
結論
進法では末尾に
個同じ数字が続くのが最大で、例えば
がそのひとつ。
定式化
ここから 進法以外に拡張する。まずは記号を定義する。
および
に対し *2、
を、
進法で下
桁がすべて
になるような平方数が存在するような
の最大値とする。ただし、そのような
が存在しないときは
、無限に大きくできるときは
とする。また、
とする。
前節の議論から、 進法の結果は以下のように書ける。
無限に長く続く条件
を固定したとき、いくらでも長くできる条件は次のように表せる。
あるいは中国剰余定理より順番を入れ替えて以下のようにも書ける。
以下この条件を満たす
奇素数
の条件
のとき、
が任意の
ここで
特に
素因数 2 の条件( n が偶数のとき)
について★が成立するのは、
が偶数で、かつ十分大きい
に対して
であることと同値。
実は は
のとき次のように書ける。
よって、★の条件は以下のように言える。なお で奇数はそれ自身の乗法逆元となることに注意。
無限になる例
前節の考察から、 が奇数のときや
の倍数のときは
とすれば無限に大きくできることが分かります。つまり、このとき
です。
例
例として 進法で考えてみましょう。たとえば
ですね。これを
となり、右辺の下
n が十分大きいとき
奇数と の倍数のときは無限であることが証明できたので、あとは
の形のもののみ。
実は が十分大きいときは、必ず
となる
が存在することが分かる。ここでは証明のイメージを理解するため、
が素数の場合を考える。このとき、
のそれぞれの区間に奇数の平方数が存在することが言えれば十分。
から、これは
つまり
程度以上なら成り立つ。
*4
結果が有限になる n
実際にプログラムなどで調べて見ると、有限の長さまでしか伸ばせないのはちょうど次の 個に限られる。
例えば の場合、
での議論から考えられる
は
の
種類、
の候補があるが、
は
の指数が奇数なのでダメ、その他も
で平方非剰余なので条件を満たさない。他の
に対しても同様に条件を満たさないことが確認できる。
なお、各 について、
進法での平方数の末尾に続く数字の最大数およびそれを満たす最小の例は以下のとおり。
| 最大長 | |||||
|---|---|---|---|---|---|
これにより、各 について末尾に続く非零の数字の最大長
は以下のとおりであることが分かった。
おわりに
ねこ:「難しかったね。」
きり:「有限の長さまでしか伸ばせないのが 個しかないというのは非自明な結果で面白かった。」
ねこ:「ありそうな問題設定だけど先行研究あるのかな。」
きり:「簡単に調べてみたけど、ここまで体系的にやっているのは見つからなかったよ。」
ねこ:「OEIS にもこの 個の整数が連続する数列は登録されてなさそうだね。」
きり:「証明も思ったより大変だった。」
ねこ:「証明できたぜみたいな顔してるけど、範囲評価のところで が素数じゃない場合の議論を誤魔化してなかった?」
きり:「ううう」
End
焼きなまし法が書けなくても AHC 赤になれたよ
AHC 赤になりました
AHC049 後に AHC Rating が 2806 になり、入赤しました。 いい区切りなのでやってたことなどをメモしておきたいと思います。
これまでの記事
入橙記事
貪欲・評価関数の工夫などは入橙記事に書いているので、興味ある人はこちらもどうぞー。
アルゴ入橙記事
推移など
レート推移

レート寄与グラフ
直近の AHC048 などの貢献が大きいようです。以前取った 4 位の AHC026 はパワーがだいぶ下がって来ている・・。

なお、このグラフは AtCoder Graphs で描画することができます。
入橙時との差分
記事タイトル
さーて、入橙時から何が変わったでしょうか?!
- 入橙時 → 焼きなまし法を使えたことがなかった!
- 入赤時 → 1 回だけいい感じに使えた!
入橙時は「焼きなまし法が使えなくても」だったが、入赤したときの AHC 049 が焼きなましだったのでさすがに「使えなくても」とは言いづらいかなー。でも実際、 AHC049 では焼きなまし法部分は自分では書かず ChatGPT くんに全部書いてもらったんだよなー。ということで、タイトルを「使えなくても」→「書けなくても」に変えてみた。 ちなみにビームサーチはいまだに書いたことも使えたこともないのでちゃんと履修しないと・・・。
これまでで良かった回
Rated の 30 位以内だとこんな感じ。
- AHC 048 [3位] New
- 戦略と盤面構築
- AHC 026 [4位]
- 貪欲
- AHC 036 [6位] New
- 戦略とDP
- AHC 018 [13位]
- 戦略と評価関数
- AHC 049 [14位] New
- AHC 032 [18位] New
- 貪欲
- AHC 029 [22位] New
- 評価関数に基づく貪欲
- AHC 025 [26位]
- 戦略
- AHC 021 [27位]
- 評価関数に基づく貪欲
( New 印は前回の入橙記事からの差分)
入橙時は貪欲が得意的なことを書いた気がするけど、最近は貪欲というよりは戦略系で稼いでいる気がする。
言語とLLMについて
入橙時と変わらず、基本 Python で書いています。これまで最高パフォを出した AHC 048 は最終提出までPythonでした。 ただ LLM で言語変換ができるようになったので、短期などでは Python で書いたものを Rust に書き直してもらうこともあります。
LLM は主に以下の場合に使っています。
その他ツール類
- pahcer: 使えてない(使いたいが環境構築が・・・)
- Optuna: 使ってない
- Copilot: 競プロではほぼ使ってない
- そもそも競プロでいつも使っている環境には入れてないので、使う場合は別の場所にコピペして補完してもらってまたコピペして戻すみたいなことをやっている
- Visualizer:
- 最近は公式のやつしか使ってない
- 公式のでもそんなに困ってないのでモチベが・・・
- これ↓とかを履修したい
- ビジュアライザ筋トレ2025年 chatGPT活用編 - yunix_kyopro’s blog
- 最近は公式のやつしか使ってない
力の入れ方について
長期では相対スコアの回が多いので、地味に 立ち回り の影響が大きい気がしています。 最高パフォを出した AHC 048 でも、これができてないとかなり順位が違ったと思います。
この記事、たぶん今回の #AHC048 ではかなり有効だった気がする。自分も途中で T 大に行きかけたけど思いとどまれた。
— きり (@kiri8128) 2025年6月9日
最上位(T 大で TOP 5 ぐらい?)以外は T が小さいケースで頑張るのが最適だったはず。 https://t.co/DosqlxrHtg
業務・職場の変化
入橙記事時点ではアクチュアリーだったので、コード書くことはありませんでした。 現在の職種はアルゴリズムエンジニアなので、何かしらのコードを書いていると思われるかもしれません。 実際は、私の場合はマネージャー寄りの立場なので、自分でコードを書いたり読んだりすることはないです。 ただ、競プロer が多い職場なので、勉強会や日々の業務の中で得られる AHC に関する知見は増えた気がします。
今後について
今後の意気込みと気持ち:
- 得意回でパフォーマンスを稼ぐ方針は引き続き継続したい
- 戦略ゲーは特に力を入れたい
- 焼きなましを LLM でサッと書いて出すみたいなのもたまにはやりたい(が、最上位に行くのはむずいのでレート的にはそんなにおいしくない気がする)
- ビームサーチもライブラリ整備して使っていきたい気持ちはある
- LLM とルールの動向が読めず、今後は取り組み方や楽しみ方が大きく変わる可能性がある
- (今の楽しみ方で)楽しめるうちに楽しんでおきたい気持ち
- レートの計算方法的に 1 位が大きく評価されることもあり、 1 回ぐらい 1 位を取ってみたい気持ちはある
- 作問もしたい気持ちはある
𝕏 入赤ポスト
AHC 赤になりました!やったー!
— きり (@kiri8128) 2025年6月21日
=====
Kiri8128さんのトヨタ自動車プログラミングコンテスト2025#3(AtCoder Heuristic Contest 049)での成績:14位
パフォーマンス:2705相当
レーティング:2791→2806 (+15) :)
Highestを更新し、五段になりました!https://t.co/THAcbzLe3R pic.twitter.com/t591poAJGp
END
AHC 045
AHC045 の参加記事です。
41.377B でプレテスト 47 位でした。
上位勢とは割と異なるところもあるので筋が良いかはあやしいですが、シンプルな実装でそれなりのスコアが出るのでご参考まで。
やったこと
- 推定とグループ構築の2段階に分けてそれぞれを解く
- 推定パート
- グループ構築
考察いろいろ
どんなクエリが良いか
長さが近い辺はどちらが長いか不明であり、長さが明らかに異なる辺は逆転が起きないと仮定する。実際、事前分布の分散をもとに、近すぎる頂点は選ばないようにしている。そうすると、クエリの検出力は、クエリの応答として現れる結果の種類数とみなせる *1 。
選ぶ点の個数ごとに、どういうクエリの検出力が高いかを見ていこう。



結果としては、正三角形をたくさん連ねる形の効率が良さそうということが分かる。
なお分散が等しい場合、小さい正三角形でも大きい正三角形でも得られる情報量は変わらない。ただし標準偏差に比べ近すぎると位置の逆転が発生し良い情報が得られないので、ある程度以上離す方が良い。
クエリによる分布更新のイメージ
サンプリングと更新は以下のようなイメージ。黒の円が事前分布。下の2点を結ぶ辺が最も近い(MST に使われなかった)ことが分かった場合、1000回サンプリングして条件を満たす点が赤い点たち。そこから推定される2次元正規分布の2σ領域が赤楕円になる。

Lが小さい場合
青の楕円はクエリ前の推定域を2σ水準で表示しています。つまり約86%はこの楕円に入ることになります。楕円でなく正方形になっている場合は、まだクエリとして一度も選ばれていないため初期分布から変化していないことを示しています。
赤の楕円はクエリ後の推定域です。クエリ前に比べて小さくなっていることから、精度が上がっていることが分かります。

Lが大きい場合
点を正三角形状に順につなげています。一気に情報が得られるのでLが小さい場合に比べてターンあたりの情報量がお得です。

グループ分け
何も良い方法が思いつかなかったので焼きなましです。
評価関数を MST の長さにすると計算量がやばいので工夫しました。
高校数学で習ったあれを思い出していました。
これ:
例えば 点
の集合に点
を追加した場合の寄与は
となるので、グループごとに や
を持っておくと
で計算できます。
結果
推定結果
Lが大きいといい感じに推定できます。Seed 2 では誤差の二乗平均のルートが60弱ぐらい。
楕円(または長方形)が2σの推定域で、点が真の位置。

Lが小さいときは全然だめです。
Seed 0 は以下のような感じ。楕円がでかすぎるせいで重なって見づらいので点0~49のみを表示しています。元の長方形からほとんど変わっていないものも多いですね。。

スコア
結果はこんな感じ。
Lが大きいときは良い感じに見える。

Lが小さいときはぐちゃぐちゃ。。

クエリはかなり盤面全体を使っている感じです。
#AHC045 Seed2 19万点
— きり (@kiri8128) April 7, 2025
盤面広めにクエリを投げています pic.twitter.com/DHzyPnoJk9
*1:実際は種類数が多いことだけが良い訳ではないですが・・。
ChatGPT コードそのままで AHC041 で 44 位が取れたよ
この記事は?
AHC 041 で ChatGPT が書いてくれたコードを貼るだけで 44 位が取れたのでやったことをメモしておきます。
前提
- プラン
- ChatGPT o1 (月20ドルのプラン)を使用
- 筆者について
- 今回 ChatGPT に頼った経緯
概要
やったことは概ね次のとおり。
- 焼きなまし法の指示を与えて Rust でコードを書いてもらう
- 温度の変化を線形→指数にしてもらう
- 差分計算するようにお願いして高速化してもらう
- 温度の初期値・最終値、イテレーション数のみを変更してコードそのまま提出
なお、本記事では最終コードを得ることに直接繋がったもののみを記載しています。 途中で試行錯誤して棄却したものなどは省略しています。
具体的なプロンプトの流れ
ChatGPT へのプロンプト全文は こちら にあります。 全文だとコード部分が長いなど流れが分かりづらいので、プロンプトとその意図などを以下で説明します。
ステップごとに、
ChatGPT o1 に投げたプロンプト
意図・感想など
の順に記載します。プロンプトは折りたたんでいるので読む際はクリックして開いてください。
ソースを貼り付け
プロンプト
AtCoder Heuristic Contestで次の問題が出たよ。これについて聞きたいんだけど協力してくれる? (中略:問題ページの HTML ソースをそのまま貼り付け) 次に質問するね。
まずは HTML のソースをそのまま貼り付けます。 質問内容をあとで変更しやすいように、一投目では質問をしないようにしています。 何も書かないとここで考察をしてきて邪魔になる *4 ので、次に質問すると伝えています。 (質問回数・トークン数 *5 の制約を考えると一気に聞いた方がお得な気もするのでこのへんは好みで。)
焼きなまし法を指示
プロンプト
焼きなまし法で実装したい 具体的には、 状態の持ち方は、 ・各頂点の親番号(根の場合は-1) ・各頂点の子のリスト ・各頂点の深さ(問題文でいう「高さ」) を持つ。 近傍は ・ある頂点の親を、別の隣接頂点に変える ・それによって高さがHを超えた頂点は独立頂点とする スコアの変動を計算し、温度関数によって採択されれば遷移する。 (N頂点全体を確認するのではなく、高さが変わった頂点のみ寄与を計算する。) これをRustで実装してくれる? あなたは優秀なプログラマーなので、必ず、そのまま提出すると先ほどの問題に正解できるコードを一発で書いてね。
ここで具体的な指示を与えました。 現時点では o1 はまだ自分で焼きなまし法のための状態や近傍を考えるのはそこまで上手ではないため、こちらで概要を指示しています。 なお、「高さが H を超えた頂点は独立頂点とする」というのは無駄が大量に発生しそうで正直いまいちな感じがして、まずは第一弾として投げてあとで近傍を再検討するつもりでしたが、案外それなりのスコアになったので結局最後までこのまま行きました。
温度関数を指数的に
温度関数を指数的に
let temp = start_temp * (1.0 - progress) + end_temp * progress; のところを、指数関数的に温度を下げる場合は、この行をどう変更すればよい?
冒頭に書いたとおり、 Rust を書けないのでこういう変更もすべて教えてもらいます。 変更箇所が分かっていて全文は不要だったのでこういう聞き方をしています。
let temp = start_temp * (end_temp / start_temp).powf(progress);
という書き方を教えてくれました。
エラーが出た
can't call method powf on ambiguous numeric type {float}
って言われた
コードテストで実行するとエラーが出たので文句を言います *6 。 すると宣言のところをこう変えてねみたいなのを教えてくれました。言われたとおりに修正すると動くようになりました。
変更後コードを共有
(略:先ほど教えてくれた行を変更したコード全文をまるごとコピー) ありがとう。こういう風にしてやってみた(どこを変えたか分かるかな)。 次に質問するね。
手元で動いたので、ついでにパラメータ(温度の初期値・最終値・イテレーション数)を変更した。 この時点で投げると 65,725,312 点(本番 510 位相当)になった。 提出コード
コードの同期も兼ねて現時点のコードを共有 *7 。
最初のところと同様、余計な考察はここでは書かないでほしい(ので「次に質問する」と書いた)が、変更箇所は把握してほしいという意図を反映しています。 すると、意図通り変更点のみ教えてくれました。
変更点を教えてくれた
主に以下の点が変更されていますね。 1. 温度を線形に下げる処理 (中略) をコメントアウトして、代わりに (中略) として、指数関数的に温度を下げる方式に切り替えている。 2. 温度パラメータ - start_temp: f64 = 3e2; (300) - end_temp: f64 = 5e-1; (0.5) のように開始温度や最終温度を大きく変更している。 3. 最大イテレーション数 - max_iter = 2000; から - max_iter = 80000; と、試行回数を大幅に増やしている。 これらの変更によって、探索範囲が広がりつつ、温度がゆっくり(指数的)に下がるようになり、より多くの近傍探索を行えるようになっています。
高速化のお願い
プロンプト
高速化するために、毎回BFSをするのではなく、差分計算でやってくれる? 各頂点vについて、深さがdからd_nextになった場合、スコアへの寄与は A[v] * (d_next - d) であることが使えるかな。
まじで Rust が読めないので遅そうなところがよく分からず。
直接の流れに影響はないので本記事では書いてないが、他にもいくつか高速化のお願いはしていた(が、あまり速くならなかったなどで棄却していた)。その中で、更新に かかるみたいなことを言ってたのでさすがにそこは改善できるなと思ってお願いした。
するとこれを実装してくれて、そのまま投げると確かにかなり速くなっていた。
お礼
プロンプト
ありがとう、良さそう!実験して、反復回数は80000→8900000(8.9e6)にしたよ。これでちょうど2秒に収まるぐらい。
700万回で投げると 1567 ms になったので、2 秒に収まるように雑に計算 *8 をして 890 万回にした。時間打ち切りでも良かったが、自分でコードいじれないのでめんどいのとシステスがないので適当に。 これが最終コードで、本番 44 位になりました。
返答
おお、そこまで回しても2秒で収まるのは素晴らしいですね! 焼きなましの反復回数を増やすほど探索精度が上がる可能性が高いので、<em>十分に高速化できている</em>のは大きなメリットです。 ただ、以下の点だけ注意してください: 1. ローカル環境とジャッジ環境の差 - AtCoderのジャッジサーバーは環境・CPU性能・並列状況が異なる場合があります。 - ローカルで2秒ぎりぎりなら、ジャッジ環境で微妙に超えてタイムアウトするリスクがあります。 2. 最適化フラグなど - コンパイル時に <code>-O</code> や <code>-C opt-level=3</code> 相当の指定で最適化しておくことは非常に重要です。 - <code>cargo build --release</code> に相当するオプションを忘れないようにしましょう。 3. ホットスポット確認 - 更なる高速化を狙う場合、プロファイラなどでどの部分がボトルネックか確認すると良いです。 - もし一度の近傍操作あたりに走る「部分木再計算」の範囲が大きすぎる場合などは、近傍設計の工夫もアリです。 もっとも、<strong>2秒台</strong>で8.9e6回も試行できるのは相当高速化されていて立派です。引き続きうまく動作するように祈っています!
なんか最後すごい褒めてくれたけど、お前が書いてくれたんだよ感しかないです。
お礼を書く意味
ちなみに ChatGPT にお礼書いて意味あるのかと思う人もいるかもです。実際、これも質問回数やトークン数を消費するので書きたくない人もいるかもしれません。
本記事では最終結果に使った部分のみを書いていますが、実際にはプロンプトをいじっていろいろ変えているので、次節のとおり木構造を行ったり来たりします。 現時点では ChatGPT の木構造遷移把握の意味での UI はいまいちなので、どれが良い結果に繋がったのか分かりづらくなります。良い結果になった場合には、何点を得られたかやお礼を書いておくとあとで戻りやすくなります。 *9
(参考) ChatGPT の木構造について
ChatGPT では、一連の会話をしているとき、過去に聞いた質問を変えて再度投げることができます。 より具体的には、例えば Q1 -> A1 -> Q2 -> A2 -> Q3 -> A3 という流れで質問・応答が続いているときに、Q2 を Q2' に変えて再度投げるようなことができ、この場合、新たに Q1 -> A1 -> Q2' -> A2' という流れができます。 新たな流れを聞いたあとも、元の流れを閲覧したり、そこからさらに質問を追加したりできます。つまり全体として質問・応答を含めたやりとりが木構造のように保管されていて、任意の頂点から変更リスタートが可能になっています。
ただ、現時点では ChatGPT をブラウザで確認する通常の方法では、現在選択している流れ(木構造のうち根→葉のうちひとつ)のみ表示される仕組みとなっていて、分岐が多い場合には木全体の把握や、別の葉への遷移はしづらい UI になっているというのが実情です。
なお、 ChatGPT のクエリ処理としては、 (Q1, A1, Q2, A2, ...) の組からなるやり取り全体に対して次に来るべき回答を生成するという処理をしているので、木構造はあくまで UI のためのものだと考えられます。
感想・考察
AHC 038
プレテスト 8.6B で暫定 318 位です。
ぱっと見解法
- 見るからに Robot Arms
- フローっぽいがいろんなフローがある
- 点集合が固まっていたらルールベースでも強そう?
最初のアイデア
上位勢の最終解法を想像すると、かなり無駄がないだろうと予想。「持ち上げる」→「回転・移動」→「置く」は基本的に 2 ターンで行うぐらいを目指す必要がありそう *2 。これは逆に 2 ターンで行けるやつをマッチングすればある程度いけるはず。
限定
クレーンの形状
このままだと考えづらいので、まずはクレーンの形を次のものに限定する。
- 根から直列にいくつか関節点をつなぎ最終点を「基準点」とする
- 基準点から直接行ける点に子(葉)を配置する
これにより、「基準点とその子たち」を移動する問題だと考えることができる。
タスク分解
- たこ焼きを持ち上げる・置く操作は最小限にする
- つまり、「初期配置にありかつ最終配置にないもの」のみを移動させる
- 各たこ焼きは(途中置き場に置くことはせず)直接最終配置まで持っていく
- 「持ち上げる」→「回転・移動」→「置く」を 1 セットとして、この単位で行動する
- つまりあるたこ焼きを持っている状態で追加で別のたこ焼きを持つことは考えない *3
- 複数のたこ焼きを持っている状態から一部だけを置くこともしない
また最後の限定により、やるべきことが小タスクに分解される。これらは任意の順番で行うことができるので、(有向) TSP に帰着できる。
平行移動
- 基準点および子たちは、平行移動のみを考える
言い換えれば、各タスクでは基準点から先の子側の形(基準点からの相対座標)は持つ時点と置く時点で変えない。これにより、移動前後の点集合が固まっている場合など、無駄なくきれいに移動できる可能性が上がる(全体を平行移動できるので) *4 。

なお、根は定義上は葉になるが、根で持ち上げる・置く操作は行わないこととした。
最初の実装
最初のサブミッションでは上述に加えさらに次の条件で実装した。
- クレーンの根から基準点までの辺を 2 つとした
- 基準点から各子までの距離は、可能な数を
として
とした
2 辺の長さを決める
基準点までの辺 2 つの長さ( )を決める。
辺の長さを固定すると、移動前の各点から移動後の各点までの移動回数が求まる *5 。移動前のたこ焼きの集合と移動後のたこ焼きの集合のマッチングのコストを最小費用流で求めることができる。 を全探索してマッチングコストが最小になるものを選んだ。
移動ベクトルごとに分解
上述の限定では、移動ベクトル( )が同じものは一気に運べるので、最もよく使われている移動ベクトル(これはひとつ前の結果を使用)に属する点が多い方が嬉しい。
これを考慮して最小費用流でマッピングする。
図示すると以下の通り。
各文字 X に対して、黒文字 X から白文字 X の移動は平行移動になっている(のでまとめて移動できる)。

A だけ見るとこんな感じ。

各移動ベクトルを 1 回で運べる量に分割しタスク化
例えば A の点たちを、いくつかのグループに分け、各グループの点はそれぞれある代表点から の長さであるようにする。
すると、問題は代表点の移動というタスクに分解できる。
TSP
各タスクはタスク開始時・終了時の状態(根の位置および各頂点の相対角度)が分かっている。
よってこれは有効辺 TSP に帰着できるので、適当に焼けば良い。
フローでできそうなこと
いろんな段階でいろんなフローが使える。使えなかったものも含めてめも。
- どの点をどの点に持っていくかというフロー
- 点から点へのコストを決めれば最小費用流で厳密解が出せる
- 点と点を結ぶだけでは直接解法にならないので、本稿で紹介したようにマッチングの際などに使う
- どの代表点をどの代表点に動かすべきかというフロー
- 代表点から代表点への移動のターン数を前計算することで、マッチングした際に動かすターン数は最小費用流で厳密解が出せる *6
- 基準点の動きを決めたあとに、どの点を移動させるべきかを決めるフロー
- 基準点の動きを決めた際に移動させられる点の個数は最大流で厳密解が求まる
- これは上述の平行移動限定に限らずできる
- 他の解法で作ったあとも、最後に 1 つ除いていけるかなど使えるかも
- 基準点の動きを決めた際に移動させられる点の個数は最大流で厳密解が求まる
感想
今回はいい解法がなかなか思いつかず、後半に予定があったこともあり、モチベが上がらなかった(言い訳)。
どちらかというと今後にも使えることをやろうと思い、いろいろ試していた。
- 提供されているツールの使用
- Copilot
- これまで競プロには Copilot を使っていなかったが、使うようにした
- アルゴだとあんまりいらない気もするが AHC だとかなりラクになった
- クラス化
- いつもよりクラス化して書いてみた(途中迷走したのできれいではない)
- ChatGPT
- Python から聞けるようにした(けど AHC に使うところまで行けなかった)
- 実行環境と連動させるのをやろうとしていた(断念した)
最初に上位を狙える方針を考えてからその方向に進むというのをやりたいところだが、今回はそれが見つからず何もできず終わるというパターンになってしまった。(1 手で結べるものをマッチングするのはワンチャンあるかと思ったけど実装したらダメなことが分かった。)
コンテスト後に TL で上位解法を見るとビムサなどの探索が多いのでどのみち苦手回だったか・・・。
End
AHC 036
AHC 036 に参加しました。
プレテスト 43,711,795,647 点で暫定 5 位です。やったことをメモがてら残しておきます。
アイデア
の構築
を決めたあとの戦略
のうち連続
マスを使う方法のみを考えると、
で最適解が求まる
- これを基本として推移しつつ、
未満のマスだけ変えた方が得になる場合にはそれを採用する
木のイメージとメリット
こんな感じで、真ん中付近にあるターミナルから各頂点を最短で結びます。

- 仮にすべての頂点をターミナルから
以内の長さで結べれば、どの遷移もコスト
以内で到達できる(総コスト
以内が達成できる)。
が大きい場合 *2
より長くなってしまっても、一旦ターミナルを経由することでどの頂点にも効率よく到達できる。
実際のケースの木のイメージ
Seed 0 ではこんなような感じ。

- 黄色がターミナル
- 黒太線が木の辺
- 青の頂点は葉
- 灰色の頂点は不要なので使わない(
に含めない)もの
- ピンクの頂点は行き先にはならないが使うもの
- 頂点の横の数字はターミナルからの距離
最終方針
の構築
- ターミナルの選定
- 行き先となる各頂点までの距離の総和(行き先になる回数で重み付け)が最小となるものをターミナルとする
- 木構造化
- ターミナルを根とする部分木であって、ターミナルまでの距離を大きくしないものを選ぶ *3
- 各頂点から、ターミナルに近付く辺をひとつ選べば良い
- ここで不要な頂点(行き先にならない頂点であって、中継点にもならないもの)は無視する
- パスを抽出
- できた木について、葉からターミナル(根)までの最短路(パス)を結ぶ
- パスを合体(長さの節約)
- これを愚直に並べたものを
としたいが、そのままだと
を超えてしまうので近いものを合体する
- 具体的には、ターミナルを
として、
と
という
つのパスがあったら
のように合体する。
- 共通部分の長さを
(この例だと
)、共通でない部分の長さをそれぞれ
および
として(この例だと
、
)、それらが行き先になる回数を
および
とする。
- ターミナルからの到達時間は、約
だけ遅くなり、全体の長さが
だけ短くできる。この効率が良い(つまり
が大きい)ものから貪欲に合体させる
- これを愚直に並べたものを
- パスの連結
- できたパスを連結して
本にする
- 連結する際は、
つをペアにして根を
つ省略する感じで
- 連結の順番は次の指標で雑に焼く
- ターミナルをまたぐふたつは、なるべく連続目的地になるものを含める
- ターミナルをまたがないふたつは、なるべく互いに隣接するものを選ぶ
- 繋げたものを
とする
- できたパスを連結して


を決めたあとの戦略
DP による理論値
- 常に幅
の区間の信号を使うと決めた場合、信号の状態としてありえるものは
通り
- 状態としては、
の区間内にある頂点が連結でない場合はそれらを区別する必要があるが、下位互換を無視すれば(実験すると)
~
程度になることが分かる。
- これらの間の辺の数は
万から
万程度なので、 BFS をすることで各状態間で移り合うために必要なコスト(信号の切り替え回数)が求まる
- さらに
を「
番目の目的地に到達した時点で状態が
であるとき、残りの目的地をすべて辿るために必要なコスト」と定義するとこれは後ろから順に最適手順を計算できる。
- なお
は
を含むもののみを調べれば良いが、これは多くの
についてあまり多くない
- なお
部分コピーが必要な場合
- 長さ
の区間すべてではなく一部だけコピーしたい場合が存在する
- これらの状態をすべて持つと状態が多すぎて
ができない
- よくあるのは、目的地に到達したあともとの道を引き返す場合なので、この場合だけ愚直に処理することにした
全体を複数回トライ
- 時間の範囲で全体をやり直して最も良いものを選ぶ
- (と言っても Python だと
周
~
ms ぐらいかかったので、
~
回ぐらいしか回らないが)
- (と言っても Python だと
ビジュアライザ
Seed 0 と Seed 23 の結果はこんな感じ(長いので最後の部分のみ)。


の経路
Seed 0 の に現れる頂点を順に結ぶとこんな感じ。隣接していない頂点に移動する場合は緑線で示しています。
(なんか緑が多くて汚い気がしたけどいい方法が思いつかず・・・)

感想・関連ポスト
- 初サブはどれぐらいかなーと思って投げただけだが思いのほか
位が取れてびびった
- そこから序盤元気マンをやるつもりだったが思ったより耐えた
- 最後の方は小手先でちょっと改善するぐらいしかできなかった(上位勢は何やってるんだ・・)
#AHC036
— きり (@kiri8128) September 2, 2024
順位表ビジュアライザ pic.twitter.com/AGckMBgvi7
正の得点を得ました。#AHC036 pic.twitter.com/GpZhUsmTFz
— きり (@kiri8128) August 25, 2024
#AHC036 おつでした
— きり (@kiri8128) September 2, 2024
プレテスト 43.7B で 5 位でした。
やったこと:
・根付き木構造にして、各葉と根を結びました(L_A に収まらない場合は類似のものを合体します)。
・後半は、長さ L_B 区間を使う前提なら DP で最適解が求まるのでそれをベースに。
END
AHC 033
AHC 033
プレテスト 47 位でした。
Seed 0 (83 点)

やったこと概要
雑貪欲です。
- タスクの優先度を決めて、優先度が高い順に貪欲に動き方を決める
- 具体的には、時刻と位置の組
(以下、単に「
次元テーブル」と呼びます)のうちどの部分を占領するかを決める
- これをたくさん繰り返す
前提(自主制約)
問題を簡単にするために、自主的な制約として次を設定する。
- 各コンテナは、搬入場所と搬出場所を除く中間位置には高々 1 回しか置かない
- 25 マスのうち下図の
マスを一時置き場にする
- これにより、搬入場所→一時置き場、一時置き場→搬出場所の移動が必ずできる
- 搬入場所から移動するときは、最初は必ず右方向に移動する(搬入場所内で上下には移動しない)
- 搬入場所(左端のマス)で上下に動くことを許すと、新しいコンテナが出現してほしくない場合に出現させないようにできるなどメリットがある
- 一方で私の方針だと実装がややめんどいのでラクすることにした

最適化方針
- コンテナの搬出の順番(優先度)を決める
- 優先度の高いものから運び出すこととすると、「直接搬出場所に移動できる」か「一時置き場に避難する必要がある」かが自動的に決まる
- 8 個の一時置き場のどの位置に置くかを適当な評価関数で決める
- これにより、やるべきことは「コンテナ
を
から
に移動する」という形のタスクに分解できる
- タスクの優先度は、適当な評価関数で決める
- 優先度の高いタスクから順に下記を行う
- 最適なクレーンを選び、そのクレーンでタスクをなるべく最短で行う(そのために必要な
次元テーブルを埋める)
- この判定は DP で簡単にできる
- 最適なクレーンを選び、そのクレーンでタスクをなるべく最短で行う(そのために必要な
- 最初のステップ(搬出順)を変えて上記を繰り返す焼きなまし
- 近傍は、隣接タスクの入れ替えと、あるタスクを可能な限り早く・遅くする、の
種類
- 数百回しかまわらないし毎回一から貪欲してるので焼きなましと言えるかは謎
- 近傍は、隣接タスクの入れ替えと、あるタスクを可能な限り早く・遅くする、の
ビジュアライザ
現時点の状況だけでなく、各クレーンがどのように動いているか(止まっているか)を認識したかったので、現時点の状況と全体の動きの両方が見えるようにした。
ビジュアライザは、動くやつを見ても訳わかんなくなるので、コマンドを並べて無駄な動きをしていないか確認してた。
— きり (@kiri8128) May 27, 2024
・止まっている
・不要に上下に動いている
・取りに行くときに右向きに動いている
が 3 大無駄な動きhttps://t.co/URTXxqwTIU pic.twitter.com/bh0MDb4Qnh
相対スコア記事について
コンテスト前に相対スコアの立ち回り記事を書いていたところ。
ごめんなさいポイント
記事では、「相対スコアを導入する場合は、ケースによって得点の取りやすさが大きく異なることが多く、単純平均や絶対スコアは全く参考にならない」という話が趣旨のひとつで、その説明のために様々な理論や例を持ち出して説明しました。しかし今回の AHC 033 に限っては、実はどんなケースでも得点の取りやすさはそんなに変わらない(トップ勢のスコア比率は通常 10~20% 程度以内、どんなにひどくても 2 倍になることはまずない)設定だったので、実は絶対スコアの単純平均や対数平均でも十分良い指標になる回でした。
もちろん、記事の趣旨を正しく理解した方は、問題設定を読みどの指標が使えるかを考えることが重要と理解されているはずなので問題なかったかと思いますが、結論のみに注目するとややミスリーディングになったかもしれません。コンテスト開始 分でやべーと思ったんですが、さすがにコンテスト中に記事を修正する訳にもいかないので残りの
時間
分はごめんなさいという気持ちで過ごしていました。
上位勢のスコアがテストケースごとにあまり変わらないことは、順位表からもある程度確かめることができます。
じろうさんが 49,971,618,357 点を出していたとき(スクショ撮ってないのでいつか忘れた)、 69 手と 72 手のやつを落としていることが分かったのでそれぐらいをターゲットにしていた
— きり (@kiri8128) May 27, 2024
一応使えたかもポイント
スコアの定義が段階的に変わる場合の説明
今回は、ターンペナルティ、順番(転倒数)ペナルティ、不正な搬出口のペナルティ、搬出されないペナルティ、と 段階のペナルティがありました。記事で書いたように、上位勢はほぼ理論値を取ってくる前提で考えてよく、そうするとターンのペナルティ以外は取らない前提で考えるのが良いです。
つまり「搬出口は必ず正しい場所から搬出しないといけない」「搬出順も必ず守らないといけない」として問題ありません。そうすることで考察や実装の手間を省くことができます。