最弱のヒューリスティッカー、最小スコアで無双する

日付は AHC 期間中になっていますが、公開日は 2025-11-25 です。

なお、この物語はフィクションであり、実在の人物・団体とは一切関係ありません。


僕の名前はレイン・ノースウェル。 ある意味で有名な競プロプレイヤーだ。

いや、待ってほしい。誤解しないでほしい。「有名」というのは、強いからじゃない。むしろ逆だ。

「最弱のヒューリスティッカー」

そう呼ばれている。

なぜなら僕は、すべてのヒューリスティックコンテストに参加しているのに、いつも、例外なく、圧倒的に――最下位を取るからである。

僕が参加しているコンテストサイトの AltCoder では、ユーザーを色でランク付けしている。 灰色、茶色、緑色、水色、青色、黄色、橙色、そして赤色。しかし僕の色は――

公式には存在しない色だ。運営が特別に用意してくれた、「レーティングがマイナスに突入した者」専用の色である。

はい、マイナスです。マイナス 278 です。現在進行形で下降中です。


また、新しい AltCoder Heuristic Contest が開催される。

いつものように参加登録ボタンをクリックすると、順位表に僕の名前が現れたからか、タイムラインに僕のことを話題にする投稿が流れ始めた。

「レイン参加してるw 今回もパフォーマンス見るの楽しみ」

「黒ランクの男、まだ諦めてなかったのか」

「あいつの提出、一回見たことあるけど、マジで意味わかんなかった」

いつものことだ。始まる前からため息が出る。別に僕が何位だっていいだろ。こっちは真面目にやってるんだから。

今回のコンテストは「施設の置き方を最適にする問題」だった。マス目の街に店をいくつか建てて、全体として人々が歩く距離や店の混み具合、建設コストをトータルで小さくする。近くに建てすぎると店同士が邪魔し合って効率が落ちるし、離しすぎると今度は移動が増える。人の流れも時間で変わるから、単純に「距離が短い所に置けばいい」ではすぐ行き詰まる――そんなタイプのやつだ。

文字通り全力を尽くした。 焼きなまし法、ビームサーチ、山登り法――あらゆる手法を試し、組み合わせた。終了五分前までチューニングを諦めなかった。

1double cost = calc_total_distance();
2// 小さければ採用する
3if (new_cost > cost) {
4    accept_solution();
5}

だけど、どの瞬間も暫定順位が最下位から上がることは一度もなく、今回もまた――

最終順位: 1247 位/1247 位

異次元的に悪いスコアをたたき出して、完全無欠の最下位である。

「やっぱりダメか。こんなにも才能がない分野ってあるんだな」

電池が切れたように座っていると、タイムラインに僕を馬鹿にするコメントが流れ始めた。 僕は画面を消した。


数日後。

僕はいつものように、電車の中でコンテストの過去問を読んでいた。

コンテストの後は毎回落ち込むけれど、だからといって諦めるつもりはない。 もしかしたら、それこそが僕に与えられた才能なのかもしれないな、なんてことを思う。

問題を読み終えた。これも一筋縄ではいかないか。荷物運び出しの最適化…貪欲法だと局所解に陥る。焼きなましで脱出するにしても、温度スケジュールをどうするか…

「…いや、案外ビームサーチでやっても、終盤まで多様性を持てるか…?」

つい独り言が口から出てしまう。

「その場合、ビーム幅が問題になるな」

突然、隣から声がした。

「え?」

見ると、赤いパーカーを着た長身の男性が、僕の画面を覗き込んでいた。

「ビーム幅を可変にするのはどうだ? 序盤は広く、終盤は絞る」

「あ…確かに、それなら計算量も…」

「メモリも節約できる。ただし、枝刈りの基準が重要だが」

「距離ベースの評価関数ですが、将来的な荷物のコストを適当な重みで混ぜてみます?」

「ヒューリスティックか。いいね」

気づけば、僕たちは熱中して議論していた。駅が二つ過ぎても、話は終わらない。

「ああ、すまない。完全に話し込んでしまった」

男性は笑った。

「俺はカナダ。元 AltCoder の赤コーダーだ」

「え…赤コーダー!?」

「引退したけどね。君は?」

「レイン・ノースウェルです。僕は…黒ランクです」

「黒?」

カナダさんは首をかしげた。

「まだコンテストに出たことがないって意味か?」

「いえ、そうじゃなくて…」

「よくわからないが、今の議論ができるなら、橙、いや赤にだってなれるはずだ」

「いや、僕はいつも最下位なので、黒ランクより上に行けないんです」

「最下位…?」

カナダさんは困惑した表情になった。

「すまない、何を言ってるのかわからない。コード、見せてもらっていいか?」

「え、ええ…」

僕はノート PC を開こうとした。

「いや、電車じゃ落ち着いて見られない。次の駅で降りよう」


駅前のカフェに入ると、カナダさんは改めて僕のコードを開いた。 スクロールしながら、じっくりと読み始める。1 分、2 分、5 分――。

「…これは」

「やっぱり意味不明ですよね。僕、本当にダメなんです」

「いや、何を言ってるんだ」

カナダさんは真剣な表情で僕を見た。

「率直に言おう。君は――天才だ」

「…は?」


「ちょっと待ってください! 僕は毎回最下位なんですよ!?」

「それがおかしいんだよ」

カナダさんはノート PC を指さした。

「君のコード、実装は完璧だ。焼きなましの温度管理、ビームサーチの枝刈り、評価関数の設計――どれも教科書に載せられるレベルだ。いや、それ以上だ。もちろん、そんなことはさっきまでの議論からも明らかだった」

「でも結果が…」

「ただ一つだけ、決定的な問題がある」

カナダさんは僕のコードの一行を指さした。

1double cost = calc_total_distance();
2// 小さければ採用する
3if (new_cost > cost) {
4    accept_solution();
5}

「この不等号。問題は距離の最小化だろう? 逆なんじゃないか」

カナダさんが > の部分を指さす。

「え…? だって、より小さいスコアを選ぶんですよね? new_cost、小なり、cost… 合っていそうですけど」

カナダさんの動きが止まった。

「…今、何て言った?」

「ですから、より小さいスコアを…」

「レイン。君、この記号を『小なり』と呼んだのか?」

カナダさんは > を指さした。

「はい。より小さい値を選ぶための記号なので…」

カナダさんは息を呑んだ。

「レイン。君は今まで、どこで競プロを学んだ?」

「え、僕の故郷です。ノースランドっていう小さな国で…」

「…ノースランド?」

カナダさんは震える声で言った。

「それって、もしかして――最果てにあるという、あのノースランドか?」

「はい、そうですけど…」

「レイン」

カナダさんは僕の目を真っ直ぐ見つめた。

「君の国では、『大きい』と『小さい』という言葉の意味が――逆なんじゃないか?」

「…え?」

「なるほどな…。いや、聞いたことがあるかもしれない。確か、ノースランドは他国との交流がほとんどないんだよな。中途半端に似ているが、独自の言語体系が発展したという。そこでは大小の言葉の意味がこちらと逆になっている――そういう噂があったはずだ」

カナダさんが早口で何か言っているが、頭に入ってこない。

「つまり…」

「君が『小さい値』と言うとき、それは僕たちの言う『大きい値』を意味する。君が『より小さい』と言うとき、それは『より大きい』を意味する」

「そんな…じゃあ、僕は…」

「問題文はこの国の言葉で『よりコストの小さい解』を求めている。しかし、これを君の国の言葉で読むと、この国基準で考えれば『よりコストの大きい解』を求められていると誤読するわけだ。なるほど、確かにそれなら new_cost > cost で正しいだろうな」

「…!」

「わかるか? 問題文が本来求めていたものと、完全に逆になっているんだ」

「つまり…」

「君は最小化問題で、ずっと最大化をしていたんだ。そして最大化問題では、最小化をしていた」

カナダさんは静かに続けた。

「そうだな。俺の見立てが正しければ――」

続く言葉で、僕の思考は真っ白になった。

「君のコード、不等号を反転させるだけで、恐らく一位を取れる


その夜、僕は一睡もできなかった。

カナダさんの言葉が頭の中で何度も響く。

「大きい」と「小さい」が逆…?

でも、確かに思い当たることがある。

ノースランドから出てきたとき、買い物で困ったことがあった。 僕はコーヒーを少しだけ飲みたくて「L サイズください」と言ったのに、店員さんは一番大きいサイズのカップをくれたのだ。 注文を間違えられたのかと思っていたけれど、今思えばそれは単に S、M、L の順番が逆だったからなのか。

そうか。 やっと謎が解けた。 それだったら、もしかして、本当の本当に――

翌日、新しい AHC が始まった。

今回は複数のロボットが狭い通路でアイテムを拾い回る迷路か。なるほど、ロボット同士はぶつかるし、アイテムには時間制限があるから順番を考えないといけない。

僕は震える手でコードを書き始めた。

いつも通りの実装。焼きなまし、貪欲法、ビームサーチ――すべてを注ぎ込む。

そして、評価関数の部分で――

1// 今までの僕なら: if (new_cost > current_cost)
2// でも、それは間違いだった
3if (new_cost < current_cost) {
4    accept_solution();
5}

不等号を、反転させた。

指が震える。

これで、本当に――?

提出ボタンを押す。

結果が表示される――

レイン・ノースウェル: 暫定 1 位

「…え?」

スコアは圧倒的だった。2 位に大差をつけている。

「まさか…」

次の提出。さらに改善。

2 位もスコアを上げているけれど、それよりも僕が更新する方が速い。

僕が 1 位をとって、あまつさえ維持できている? 一度も最下位より上になったことがない、この僕が?

3 時間経過。4 時間経過。5 時間経過。

僕は夢中でコードを改善し続けた。

最終結果――

レイン・ノースウェル: 1 位 / 1834 位

パフォーマンス: 3521


「信じられない…」

ディスプレイの前で、僕は呆然としていた。

レーティンググラフが急上昇している。マイナス 278 から一気に――初めて黒色を脱出し、赤色に向かう軌道を描いている。

今回のパフォーマンス: 3521

初めて、黒以外の色が表示された。

スマホが鳴った。カナダさんからのメッセージだ。

「よくやった、レイン。君は本物の天才だ。今まで君が磨いてきた技術と感覚、それらはすべて正しかった。最後のピース――大小のすれ違いが埋まった今、もう君は誰にも負けない。――おめでとう、世界最強」

涙が溢れた。

僕は無能じゃなかった。

住んでいた世界が逆だっただけだった。

僕は――ようやく、正しい世界に立つことができたんだ。


数ヶ月後。

僕は「逆転のレイン」という異名で呼ばれるようになった。

世界ランキングも急上昇している。それもそうだ、あれから毎回 1 位か 2 位しかとっていないのだから。 レーティングが金冠に届くのも時間の問題だろう。

そして今日も――。

「おい、レイン。今回のコンテスト、お疲れ」

カナダさんから電話がかかってきた。コンテストが終わって、感想戦の時間だ。

「お疲れさまです。グラフの彩色問題、難しかったですね」

「ああ。制約が厳しかったな。俺は分枝限定法をベースに、ちょっとサボって軽量化する感じで攻めたんだが…」

「分枝限定法…! それ、間に合うと思いませんでした。僕は貪欲で初期解作ってから、焼きなましと局所探索で頑張りましたね」

「やっぱり焼きなましか。近傍はどうした?」

「色交換と頂点の再割り当てが中心です。あと、時々キックしたり、ちょっと大きめの破壊再構築も入れてますが」

「さすがだな、レイン。調整して持っていく力が違う」

電話越しに、カナダさんの笑い声が聞こえる。

「今回はお前が 1 位だったな。俺は 2 位だ。完敗だよ」

「いえ、カナダさんの解法の方がエレガントでした。僅差でしたし」

「僅差でも負けは負けだ。次のコンテストこそ、俺が 1 位を取り返すからな」

「望むところです」

「いい返事だ」

カナダさんは少し間を置いてから、続けた。

「なあ、レイン」

「はい?」

「俺さ、引退したのは。つまらなくなったからなんだ」

「…え?」

「1 位ばかり取ってると、だんだん刺激がなくなってくる。いつの間にか、コンテストに出るのも億劫になって、気づいたら引退してた」

カナダさんの声は、どこか懐かしむような響きだった。

「でも、あの日、電車でお前の独り言を聞いたとき――久しぶりに心が動いたんだ」

「カナダさん…」

「お前、とんでもなく真剣な顔してたからさ。なんか昔の自分を思い出してな。気づいたら話しかけてた」

「はい」

「で、話してみたら、思考の深さも速さも一流。それが黒ランクで、その理由がまさか、符号が逆だっただけとはな。そんなことあるかよ」

カナダさんは笑った。

「天才が、初心者みたいな理由で黒ランクに留まってた。正直こんな話、今でも誰も信じないと思うぞ」

「笑わないでくださいよ。本当に本気だったんですから」

「悪い悪い」

カナダさんがふと真剣な顔になる。

「強い人の誰か一人でも、お前のコードを見てたら、もっと早く――」

「…」

「…まあ、最下位のコードなんて誰も見ないよな。俺だって現役のときは、上位のコードばかり追いかけてたもんだ」

カナダさんは苦笑した。

「実力を数値化して称えるシステムが、逆に才能を埋もれさせてしまったんだ。皮肉なもんだな」

「でも、カナダさんは気づいてくれました」

「電車で隣に座って、たまたまお前の独り言が聞こえた。それだけの偶然だ」

カナダさんは笑った。

「でもそのおかげで、俺はまた本気で競える相手を見つけた。お前との勝負が、楽しくて仕方ないんだ」

「カナダさん…ありがとうございます」

「いや、礼を言うのはこっちだ。あのときお前の独り言を聞いたこと――それが俺の人生も変えた。ありがとう、レイン」

「こちらこそです。カナダさん」

「じゃあな、レイン。次のコンテストで会おう」

「はい!」

電話を切る。

僕は画面に向かって、コードを書き始める。

カナダさんとの議論で、また新しいアイデアが浮かんだ。

「大きい」と「小さい」。

その意味を正しく理解して。

そして今、僕には――対等に競い合える仲間がいる。

“Sometimes, the greatest discoveries come from looking at what everyone else ignores.”

『時に、最大の発見は、誰もが無視するものの中にある』

――これが、僕の世界。

おまけ SS - 電車の中で

あの日から一年後。

レインは電車の中でノート PC を開いていた。

今日は 2 位だった。カナダさんには僅差で負けたが、楽しい戦いだった。

ふと、隣の学生が独り言を呟いているのが聞こえた。

「…この評価関数、もっと小さい値になるように…いや、そもそも何でこんなにスコア悪いんだろう…」

レインは思わず画面を覗き込んだ。

そのコードに見覚えがある。

不等号が、すべて逆だ。

レインは、一年前の自分を思い出した。

独り言を言っていた僕に、誰が何をしてくれたんだっけ。

今度は――。

「すみません」

レインは声をかけた。

「君、もしかしてノースランドの人?」

<完>


アイデアを渡し、Claude 4.5 Sonnet に書いてもらい、私が編集しました。

なお、この世界の AHC は 6 時間のようです。