脳筋ぱそこんおゆうぎ!

虫おたくがぱそこんであそぶにっきだよ!

デスクワーク一年目の反省文

 昨年4月に人生初のデスクワークに転職してから、ちょうど丸一年経った。

 健康面で何かと失敗が多い一年だったので、その反省をここに垂れ流しておく。

 

 わずか1年で大学から除籍された19歳の春から去年の春に至るまでの約十年間、私は様々な仕事を転々としてきたのだが、ハタチ前後にバイトしていたコンビニを除けば、農業用ハウス工事の現場作業員や環境調査員などの肉体労働がほとんどだった。いずれもアルバイトか個人事業主で、会社員の経験は一切無かった。

 

 農業用ハウスの工事現場で働いていた頃は、煤にまみれて太陽の下で汗を流す毎日。給料を貰いながら筋トレしているようなもので、わざわざ高い金を払ってジムなんか行かなくたってピットブルのような筋肉バキバキボディを維持できていた。

 5年ほど前に環境調査員になってからは肉体的負荷がずいぶんと減り、大幅な収入増で気軽に外食に行けるようになったこともあって、急激に太ってしまった。それでも山や海で毎日肉体を動かしていた分、そこらのデスクワーカーよりは健康だったんじゃないかと思う。

 

 そんな暮らしに転機が訪れたのは2022年の冬だ。ボルダリング中にうっかり転落してしまい、マットの中で右足首を思いっきり捻挫してしまったのだ。靭帯が伸びて足首が不安定になり、しばらく山を歩けなくなってしまった。

 幸いなことに当初の予想よりは早く回復し、一ヶ月半後には現場に復帰できたのだが、この期間は完全に無収入!

 零細個人事業主の身ゆえ、傷病保険の類も使えなかった。

 

 身体を壊せば終わりというリスクなんざ承知の上で一生現場仕事で食っていくつもりだったのだが、実際にケガをして長期間の無収入を経験したことで、恥ずかしながら弱気になってしまった。

 

 「ここらが潮時かな、正社員のデスクワークに転職しようか...」

 

 2023年に入ってすぐに転職活動を始めた。この時点で28歳半。もう三十路目前だというのにそれまでマトモな就活を一度もしたことがなく、面接の常識なんて何も知らなかったのだが、幸運にも短期間で内定を頂き、その年の4月から正社員のプログラマーとして働くことになった。東京の会社なので、沖縄在住の私は必然的に常時リモートワークとなった。

 

 「これはプライベートできちんと運動しないとヤバいぞ...」と入社当初は危機感を抱き、朝のランニングを頑張っていたのだが、梅雨にいったん中止してから一気にランニング習慣が崩れてしまった。

 梅雨が明けてもランニングをサボり続けた。たまにやる気が出て走る日もあったが、間が空くと当然ながら筋肉痛がひどく、痛みが引くのを待っているうちにサボり癖が再発することの繰り返し...。

 

 2020年頃までの体重が60kgで、その後一気に太って現職に就いた2023年4月時点では72〜75kg前後。

 それがなんと、12月頃には79.8kgまで増えてしまった!

 鏡に映る自分の顔はアンパンマンのように真ん丸で、痩せていた頃の写真と比べると、もはや同一人物には見えない...。

 

2018年、痩せていた頃の私

 

2023年、激太りしてピザになった私の共食いシーン

 

 また、日光を浴びる時間が激減したことが原因なのか、なんとなく精神的に不調な日が増えていた。何か具体的に嫌なことがあったわけでもないのに気分が沈み、趣味の開発や昆虫採集に取り組む気力が湧いてこない日が多くなってしまった。

 

 年が明けて2024年。正月休みが終わる頃、私はこんな作戦を思いついた。

 「いきなりランニングしたって続かない、まずは散歩から始めよう!」

 

 この作戦は大当たりだった。早起きして近所の山道や沢をのんびり歩くのは実に気持ちよく、年明けから3月末現在まで無理なく続けることができた。

 

おさんぽルートの沢

 

おさんぽルートから海を見下ろす

 

 一時は80kg寸前まで増えていた体重が久々に75kgを切った時は、思わず体重計の上で喜びの声を上げてしまった。

 日光を浴びる時間が増えたおかげなのかは分からないが、気分が沈む日も減ってきた。

 このまま散歩を継続していけば、少しずつ元の身体に戻れるかもしれない。まだまだ先は長いが、ようやく希望が見えてきた。

 

  以上、長々と話してきたが、デスクワーク一年目の反省をまとめるとこんな感じだ。

  • プライベートで運動しないとマジで一気に太る
  • 急にハードな運動をすると続かない
  • 低負荷な運動でも続けていれば痩せてくる
  • 引きこもりの才能に欠ける凡人は、外で肉体を動かさないと心がちょっと病む

 いやまあ、こうして並べてみると当たり前のことばかりなのだが、長年肉体労働に従事していた私にとっては新鮮な失敗と反省なのだ。

 

 明日からはデスクワーク二年目が始まる。この調子で無理なく運動を続けて、心身ともに元気に長く働きたいと思う。

遠回り入緑記 〜この道、寄り道、廻り道〜

はじめに

 2023年12月16日のABC333で初めての5完をキメて、念願のAtCoderアルゴ入緑を果たしました!

 コンテスト終了約1分前にE問題を通した瞬間の興奮を思い出してはニヤニヤしています。ちなみにEを提出したのはコンテスト開始から99分9秒後です。333の3倍ですよ。なんという偶然。

 翌週のABC334には予定があって参戦できないので、私にとってはこれが今年最後のコンテストでした。今年の抱負であった年内入緑をギリギリで達成できて一安心です。

 累計AC数はこのコンテストでちょうど700となりました。アルゴコンの参戦回数はこのコンテストで99回目。妙にキリの良い数字ばかりですね。

入緑時点のレーティンググラフ

入緑時点のAC数など

 

 「平方根ってなんだっけ?」状態で競プロを始めてから2年、入茶してからは1年半、やっとになれました。いやぁ、長かったですねぇ。感慨もひとしおです。

 

 さて、色変記事といえば「どんな精進をしてきたか」を語るのが主流ですが、正直なところ私はあまり語れることが無いんですよね。ろくに精進してませんし。

 ひとつ言えるのは、コンテスト後に復習をするようになってから伸び始めたってことですかね。

 今年の春、足のケガをきっかけに環境調査業からプログラマーに転職したのですが、会社の競プロ部で毎週ABCの復習会をやるようになってからジワジワと成績が良くなってきました。自律的に精進できない怠け者の私にはとてもありがたい環境です。部員の皆様には感謝しております。

 ※ちなみに転職先がすんなりと決まったのは競プロのおかげです。AtCoder事務所の方角に足を向けて寝られません。

 

 精進についてはこれくらいしか語れることがないので、ここからは私が競プロをしながら愛用の原付クロスカブで巡った島々を紹介していこうと思います。行った島すべては載せきれないので、特に印象深い島を選びました。

 

 この先を読む際におすすめのBGMは、吉田拓郎の「人生を語らず」です。 

youtu.be

 

黒島

 同名の島は全国各地にありますが、ここでご紹介するのは鹿児島県三島村の黒島です。他の黒島と区別するために薩摩黒島と呼ばれることもあります。

 小さな島ですが標高は高く、最高峰の櫓岳は標高620mです。低標高地は牛の放牧場と竹林が多いですが、高標高地は深い樹林に覆われています。

 私が住む沖縄本島からフェリーを乗り継いで行くと片道約30時間。電波が良い時には競プロの問題を解いて、長い船旅の暇を潰していました。

 

黒島の島姿

荷物満載のクロスカブ 黒島にて

黒島の山道

黒島の峻険な山姿

船内で競プロ

 

硫黄島

 映画で有名なあの硫黄島ではなく、先ほどの黒島と同じく鹿児島県三島村に属する島です。

 火山活動が活発で、山肌からはその名の通り硫黄が吹き出しています。険しくも美しい島姿です。

 この旅では日程の都合で残念ながら上陸できなかったのですが、また三島村を訪れる機会があれば次こそは上陸したいですね。

硫黄島の火山

褐色に染まった硫黄島の港

 

座間味島

 沖縄県慶良間諸島に属する島です。ダイバーたちには人気があるようで、多くの観光客で賑わっていました。

 この島でキャンプしながら参加したABC244で初の4完をキメました。ただ、このコンテストのD問題はとても簡単で灰diffだった上にお腹を冷やしてトイレに籠もったりしていたので、せっかく4完したのになんと灰パフォでした...。当時はちょっとヘコみましたが、今となってはこれも良い思い出です。

座間味島

座間味島のキャンプ場でABC244に参戦

 

伊平屋島

 沖縄本島のすぐ北に位置します。山が多く森が豊かで海も澄み切っている静かで美しい島です。沖縄の島々の中でも一番好きかもしれません。

 頻繁に訪れているこの島では何度もコンテストに参戦しました。ARC143で珍しく0完回避して温まったり、ABC289に飲酒Rated参戦してBまで解いた後に寝落ちして冷えてしまったり...。

 勝っても負けても、この島で味わう競プロは格別です。

伊平屋島の島姿

伊平屋島 腰岳展望台からの眺望

伊平屋島 腰岳林道からの眺望

伊平屋島 米崎キャンプ場前の海

夏の伊平屋島

伊平屋島 島の北側から海を眺める

伊平屋島 島の北側はほぼ無人地帯となっている

伊平屋島の磯

 

伊平屋島から橋で渡れる野甫島の港 港内とは思えないほど澄んだ海

伊平屋島の最高峰・賀陽山の山頂から伊是名島を眺める

伊平屋島 岩山の上でお腹をさする私

伊平屋島 ABC開始直前

伊平屋島 朝の海を眺めながらゆったりと競プロ精進

 

おわりに

 藪をかきわけ山奥を歩いている時に、木々の隙間からちらりと海が見えることがあります。深い緑の奥に微かに見える水色。この瞬間がとても好きです。

 AtCoderジャングルでも同じ光景が見たいですね。水色に届きたいです。

 というわけで、次なる目標は2024年中の入水です。まだまだ遠いですが、やってやりますよ!

 藪漕ぎ上等!!

 

スコア計算完全放棄!AHC017お手軽解法

はじめに

 AHC017(AtCoder Heuristic Contest 017 THIRD プログラミングコンテスト 2022)お疲れ様でした。念願の初青パフォをキメて入水できたので私にとっては大満足のコンテストでした!

 今回の問題ではスコア計算の重さに苦しめられた方が非常に多かったようですね。それでも上位勢の方々は代表点の選定やスコアの差分計算など様々な工夫を凝らして山登り法や焼きなまし法をうまく適用していたようです。

 しかし悲しいかな、私はしがないアルゴ茶コーダー。高速化の勘所なんて分かりません。そこで今回は思い切ってスコア計算を完全に放棄してみました。

 本記事ではスコア計算を一切せず、アルゴリズムもごく初歩的なものしか使わず、実装面で難しいところも特に無い、そんなお手軽ながらそれなりに良い成績(提出者994人中194位、1712パフォ相当)を出せる解法を解説します。

 名付けて最短経路クラスタ解法(ちょっとカッコつけました...)です。

 

概要

 最短経路には(少なくともこの問題においては)以下の3つの性質があるっぽいです。これらを利用します。文献でちゃんと確かめたわけではないですし、私が何か勘違いしているだけの可能性もあります。あくまで「ぽい」であることをご了承ください。

 ※ランダムに選んだ2点のペアとその2点間の最短経路を多数生成しているという前提で

  1. 各辺が最短経路に登場する頻度には偏りがあるっぽい
  2. 最短経路で一緒に使われやすい辺のクラスタがあるっぽい
  3. 同日に工事する辺のクラスタをなるべく統一した方が代替経路への影響が小さいっぽい

 今回利用するのは性質2と3です。1についても上手な使い方があるのかもしれませんが、私はうまく活かせませんでした。

 性質3が成り立つ理由については正直なところ正確に理解しているわけではないのですが、おそらく以下の2点だと思われます。

  • 工事の影響を強く受けてしまう最短経路を絞れる
  • 上位陣が使っていた工事辺一本道作戦に近い形にできる

 ビジュアライズしてみると、工事辺が一本道に近い形で密集していることが分かります。※ここでは見栄えを優先して、かなりうまくいったケースを採用しています。

seed = 30 一本道


 工事辺付近以外の点(紫の点)をクリックしてみると、工事の影響が小さいことが分かります。ほとんど青(影響なし)ですね。

seed = 30 工事の影響が小さい点

 工事辺上の点をクリックしてみた例がこちら。流石に影響が大きいものの、その範囲が限定的であることが分かります。

seed = 30 工事の影響が大きい点

 

具体的な手順

 ※各工程の時間配分は私の愛用言語であるCrystal(静的型付けで高速なシングルバイナリを吐ける見た目はRubyっぽい言語、ただしC++よりは基本的に少し遅い)で実装することを前提としています。他の言語の場合は適切な時間配分が異なるかもしれません。

  1. 乱択で2点のペアを大量生成し、2点間の最短経路をダイクストラ法で求める。この際に各ペアに番号をつける。この番号をクラスタ番号とする。各辺が所属する(※複数所属可能)クラスタ番号を格納する配列を用意し、最短経路においてその辺が使われる度にクラスタ番号を配列に追加していく。この工程には1秒使用する。
  2. 各辺が他の辺と共に所属するクラスタの数を記録していく。
  3. 一日あたり(辺数 ÷ 工事日数)個の辺を適当に割り振り、初期解を作成。
  4. 2つの工事日をランダムに選び、両日からランダムに選ばれた辺を交換する。それによって各日の工事辺同士が共有するクラスタ番号が増えるなら交換を確定し、そうでないなら戻す。この工程には0.7秒使用する。
  5. 2つの工事日をランダムに選び、一方の工事日からランダムに選ばれた辺をもう一方の工事日に移す。移された日の工事辺同士が共有するクラスタ番号が増えるなら移動を確定し、そうでないなら戻す。この際、一日あたりの工事可能辺数の制約を超過しないように注意する。また、辺を移しすぎるとスコアが悪化するため、辺の増減可能数に制限を課す。この工程には0.3秒使用する。
  6. このままだと到達不能点ができてしまうので、各日の工事辺上の2点に対してDFSを用いて到達可能性を調べ、到達不能な場合は他の日とランダムに辺を交換して到達可能にする。この工程には念のため余り時間のほぼ全て(3.8秒)を使用する。
  7. 各辺の工事日を格納した配列を文字列化して出力。

 ※なお、工事日数が少ない場合(工事日数6日以下)はこの解法が逆効果になるケースが多いため、乱択で各日の工事辺同士の距離を伸ばした上で仕上げに到達不能点を解消するのみとする。これは上位陣の一本道作戦とは逆のアプローチであり、極端に悪いスコアは出ないものの良いスコアも出なくなってしまうため、改善の余地あり。

 私の実装はこちら。コード自体は汚いですし命名センスが致命的に無いのがお恥ずかしいのですが、一応コメントは多めにつけているので是非読んでみてください!(やっぱり恥ずかしいから読まれたくないかも...)

atcoder.jp

 

この解法のメリットとデメリット

メリット

  • 何の工夫も無い教科書的なダイクストラ法とごく普通のDFS程度しか使用しておらず、アルゴリズムの知識が少なくても容易に実装可能
  • スコア計算が不要
  • 工事日数が多く頂点数および辺数が多いほど強くなる(おそらく最短経路パターンの多様性が高まるから)傾向があり、これは1位を含む最上位陣の多くとは真逆なため、相対スコアが伸びやすくなる ※コンテスト中に工事日数が少ないケースにバグを含むコードを提出してしまい絶対スコアが大きく悪化しても相対スコアへの影響が非常に小さかったことから、この事実にコンテスト中の時点で薄々感づいていたことを自慢させてください

デメリット

  • この解法をベースにさらなる改善を施すことが困難で、局所解的な性質を持ってしまっている
  • 工事日数が少なく頂点数および辺数が少ないケースに極端に弱い(特に前者は致命的に悪影響を及ぼす)ため、絶対スコアは悪くなりがち(コンテスト後に確認してみたところ、私より順位がかなり低い方々にも絶対スコアでは負けていることが少なくなかった)

 Wladimir Leiteさん(wleite)が作成してくださったこちらの表を見ると、私の解法が工事日数の多さに強く依存していることがよく分かります。

tc-wleite.github.io

 

おわりに

 解法の多様性ヒューリスティックコンテストの大きな魅力だと思います。

 たとえば一言に焼きなまし法と言っても「何を良い状態と定義し、どう遷移させ、如何に高速化するか」にはたくさんの選択肢があり得ますし、その過程で使用する典型アルゴリズムや名も無き即興アルゴリズムの組み合わせは十人十色です。

 また、焼きなまし法が多くのコンテストにおいて強力で最上位勢の解法で多用されていることは確かですが、それ以外にもそこそこ強いヘンテコな解法を生み出す人がいたり、結果は伴わずとも面白い特徴を持つ解法を生み出す人がいたりして、コンテスト後のTwitter競プロTLは同じテーマの元で個性的な絵を生み出す合同展の様相を呈しています。

 結果の良し悪しに関わらず、参加者全員の解法に強みや弱み、そして面白みがあるんじゃいかと思います。それがTwitter上での刹那的な消費で流れてしまうのは何だかもったいないので、もっと多くの方がご自分の解法を記事として書き残して頂けると嬉しいです!

広義環境構築2022

はじめに

 2022年も間もなく終わりですね。早いものです。

 今年はPC内・物理ともに環境構築にかなりリソースを割きました。その甲斐あって今は私史上最高に快適な環境でパソコンカタカタオタクライフを満喫できています。

 本記事ではソフトウェアからハードウェア、果ては部屋の飾りにいたるまで、広い意味での環境構築の一例をご紹介いたします。

 

ソフトウェア編

Vim

 高校時代はEmacs信者、沖縄に移住して虫採りに夢中になりプログラミングをする時間が減ってEmacsキーバインドを忘れてからはVSCodeを使っていた私ですが、今年からVimに乗り換えました。理由は色々ありますが一番はなんとなくカッコいいからです。

 これまでVimSSHVPSを操作する時くらいしか使っておらず、その程度ではVimの独特な操作体系に慣れるはずもないので「モードだのなんだの面倒くさいエディタだな」くらいに思っていました。しかしいざ本格的に使用してみると、あまりの快適さに驚きました!

 優れたキーバインドとテキスト編集機能だけではなく、起動の速さも快適さの向上に貢献しています。これまでEmacsVSCodeなど起動が遅めのファットなエディタばかり使っていたので、コードを書きたくなってもエディタを立ち上げるのが面倒で億劫になってしまうことが多々あったのですが、Vimに切り替えてからは実に軽いノリでエディタを開けるようになりました。

 vim-airlineなどを導入すれば見た目も大変オシャレになります。カラースキームはicebergとNordがお気に入りで、最近は雪山への憧れが止められなくなってしまった(哀れな沖縄県民)のでNordをメインで使っています。ターミナルもNordにしました。

 私のオシャレなVim、見てくれよな!!

Vim

 

i3wm 

 タイル型ウィンドウマネージャのi3wmを導入しました。正確に言うと私が使っているのは派生版のi3-gapsです。高校時代にもちょろっと試したことはあったのですが、本格的に使い始めたのは今年からですね。理由は色々ありますが一番はなんとなくカッコいいからです。

 まずね、見た目が最高。なんかスーパーハカーって感じするじゃん!

 そして操作性も最高。ウィンドウが自動的にポンポン分割されて出てくるのが快適すぎる。例えば画面左半分でブラウザを開き右半分はVimとターミナルで半分こみたいなお決まりのウィンドウ配置もチマチマ調整することなく瞬殺で作れます。全ての操作を簡素なキーバインドで完結できるのも最高です。キーボードだけでパソコンカタカタするのスーパーハカーって感じするし!!

 

i3wm

 

Arch Linux

 UbuntuからArch Linuxに乗り換えました。理由は色々ありますが、一番はなんとなくカッコいいからです。Archはインストール時点では最小限のパッケージしか入っておらず、ウィンドウシステムすら無い状態から環境構築を始めることになります。マトモに使えるようになるまでは少々手間がかかるのですが、余計なものは一切入れずに自分好みの環境を自由に構築することができます。かつてはインストールが難しいと言われていましたが、今はさほど難しくありません。なんとなく怖がってる方も是非気軽にインストールして自由を手に入れちゃいましょう。

Arch Linux

 

Vimium

 ウェブブラウザをVimっぽいキーバインドで操作するプラグインです。ChromeFirefoxで利用可能です。ブラウザをほとんどキーボードだけで操作できるようになります。快適ですよ。

 私のようにブラウザとVimとターミナル以外ほとんど開かない人間の場合、このプラグインを導入することでキーボードから手を離す回数が激減します。もうね、ドラマの主人公とか電脳コイルのガキどもみたいにず〜っとキーボードだけをカタカタできちゃう。これで気分は完全にスーパーハカー!!

Vimium

 

ハードウェア編

MD770とHHKB

 2020年から分割キーボードのMD770を愛用しています。コイツのおかげで肩と背中の痛みが無くなったのですが、少々かさばるので外出先では使いにくいです。私はよく虫採りや仕事のついでに山の中で競技プログラミングをキメるのでこれは困ります。かといって今更ノートPCのペチペチキーボードなんて打ちたくないので、携帯しやすい小型キーボードを導入することにしました。そこで選ばれたのは、言わずと知れたキーボード界のスター・HHKBです。

HHKB in 山奥肉体労働現場

 噂に違わぬ最高の打鍵感の虜になった私は、外出先のみならず自宅でメインPCを使う時も気分次第ではHHKBを使うようになりました。ただ、やはり分割キーボードでないと長時間の使用がツラいのでMD770も手放せません。こうなりゃ両方使うっきゃねえ!!そう考えた私が編み出したスタイルがこちらです。二刀流じゃい!!!!

HHKBとMD770の二刀流

 まあでもこれスペースの無駄が大きいですし、HHKBが割れてくれるのが理想なんですけどね。PFUさん、たとえ10万円でも絶対に買うので割れたHHKB作ってください!!お願いします!!

 

アステージ アクティブストッカー600

 愛車クロスカブを納車してから3年間ホンダの純製ボックスを使っていたのですが、4年目となる今年はアステージの大型ボックスに切り替えました。見た目と容量が目当てだったのですが、思わぬ使い道が見つかりました。そう、スタンディングデスク代わりにちょうどいいのです。

 アウトドア競技プログラミングがより快適になりました!

俺流スタンディングデスク

 

装飾編

モルカークッション

 モルカーは救済であり、人生という長い苦行の雲間から射し込む一筋の光

モルカークッション

 

やんばるの生物置物

 沖縄島北部地域、通称やんばるの森の生き物たちの置物をモニター前に置くようにしました。かわいいですね。

やんばるの生物置物

 

田中一村の絵

 田中一村(たなか いっそん、1977年没)は奄美大島の自然をテーマにした作品で知られる日本画家です。以前奄美大島に行った時に田中一村記念美術館で購入した額絵シートをモニタの上に貼ってみました。また行きたいな...。

田中一村の額絵シート

 

ミーハー虫箱

 私は主に数mmの地味な甲虫を採っているので、肉眼で楽しめる見栄えの良いミーハー虫はあまり持っていませんが、手持ちの数少ないミーハー虫を集めた標本箱を1つ仕上げてみました。

 サブPCデスクの上に置いて、PC作業に疲れた時はパッと取り出して標本を眺めて思い出に浸れるようにしています。

標本箱

 こちらはコロナ禍直前の2020年1月にタイで採ってきたコアオアトバゴミムシです。美しすぎる。またタイ行きタイ....。

タイで採ってきたコアオアトバゴミムシ

 

おわりに

 世界のどこにも需要が無さそうな自己満記事にお付き合い頂き、ありがとうございました!!本当はもっと細かいことまで書きたかったのですが、疲れてきたのでこのへんにしておきます。

 来年もさらなる快適環境を目指していじくりまくろうとと思います。

 それでは皆様、良いお年を!

焼き払え!!皆解会002(AHC002バチャ)参戦記

はじめに

 皆解会(AHCをみんなで解く会)とは、その名の通りAtCoder Heuristic Contest(AHC)の過去問を皆でワイワイ解くDiscordグループです。歯科医競プロerとして知られるainemさんが主催しています。

 第2回となる今回は、AHC002(本来は4時間の短期個人戦コンテスト)を10日かけてチーム戦で解くバーチャルコンテスト(以下バチャと省略)でした。短期コンテストの問題をチームメイトたちと相談しながら長い時間をかけて解くので当たり前と言えば当たり前なのですが、全チームがバカ高いスコアを叩き出し大怪獣バトルの様相を呈していて非常に楽しかったです。

 そんな激しい戦いの末に我がチーム「三尺秋水」は残念ながら最下位となってしまったのですが、ずっと憧れていた焼きなまし法を初めてマトモに使えたので、個人的には大満足のバチャとなりました。バチャくらいでわざわざ参戦記書くの!?というツッコミが聞こえてきそうですが、初めてちゃんと焼けた喜びの記録を残しておきたいので書いちゃいます。

 さて、本題に入る前に一つ注意事項があります。本記事の趣旨は「初めて焼きなましできて嬉しい!!!!」ただそれだけです。焼きなまし法についての実用的な記事ではございません。ただの日記です。私の「嬉しい!!!!」という叫びをちょっと解像度上げて長ったらしい記事に仕立てただけです。実用性は期待しないでくださいね!!

 

Before Contest

 まずはチーム決めです。30人の参加者が6チームに分けられます。今回のチーム決めはざっくり言うと「チーム内での議論が成立しやすいように、概ね実力が近い者同士で分ける」方式なので、チーム間での実力差がかなり出てしまいます。

 我がチーム(チーム番号1番)は最弱です。青パフォ経験者が一人もいない唯一のチームとなりました。メンバーはスコティさん、がぶりえるさん、rinrionさん、jabeeさん、そして私です。

 メンバーの次はチーム名決めです。我々が最弱であることを念頭に置いたチーム名にしたいですね。私が出した案は以下の通りです。

 

 ポジティブ(下剋上)路線

 ネガティブ(敗北の美学)路線

  • テルモピュライ(少数のスパルタ軍が大国ペルシアを相手に善戦するも最終的に全滅した戦いの地)
  • 落日燃ゆ(城山三郎の小説のタイトル。個人的には敗北の美学を感じます)
  • 能島村上水軍(敗北の美学のカタマリです。城山三郎「秀吉と武吉」を読んでね)
  • あしたのジョー(真っ白に燃え尽きたいよな)
  • 八重樫(ボクサーの八重樫東から。めちゃ強かった頃のロマゴンを相手に勝てない戦いに挑み、美しく散りました)

 

 えー、明らかにネガティブ路線の方が早口のオタクになってますね。ご察しの通り私は敗北の美学が大好きです。なんかカッコいいじゃん。

 私としては「落日燃ゆ」が最推しだったのですが、最終的には多数決でスコティさんが提案した「三尺秋水」に決定しました。意味はググってください。めちゃくちゃカッコいいです。

 全チームのメンバー表はこちら。どのチームも良い感じのチーム名とロゴですね。我ら「三尺秋水」と「ひよこ」の毛筆ロゴはainemさんのご友人に書いて頂きました。カッコいい!!

 チーム名の議論を通して十分にアイスブレイクできたところで、12月2日、いよいよコンテスト開始です!!!!

 

競プロerなら、負けると分かっていても戦わねばならないときがある

 

1日目 12月2日(金)

 所用で関東に行っていたので私は何もしていません。

2日目 12月3日(土)

 同上

3日目 12月4日(日)

 同上

4日目 12月5日(月)

 週末に何もできないまま平日になっちゃいましたね。でも大丈夫です。何故なら先週ボルダリング中に落っこちて右足首を痛めてしまい長期休職中なので!!

 私の仕事は山や海での肉体労働なので、ちょっとした怪我で仕事ができなくなって収入が消えます。今月の収入はほぼゼロになりました。貯金なんて一切無いですし、会社員ではなく社会保険にも入っていないので傷病手当金なども貰えません。マジで来月生きていけるのか!?非常に不安ですが、まあおかげでこのバチャに集中できるぜ!!!!!!!(ヤケクソ)

 いざ戦闘開始!!まずはビジュアライザのマニュアルモードで遊びながら問題の雰囲気を掴みたいところ。しばらく遊んでみて思ったのは、目先の移動先マスのスコアより経路の長さの方が最終的なスコアへの影響が大きそうということです。かといって実行制限時間内に真の最長経路を求めるのも無理そうですね...。

 色々試しましたがマトモなスコアを出せることなくこの日は終了です。これ本番ルール通り4時間だったら何もできずに終わってましたね...。

 

5日目 12月6日(火)

 この時点で私は何故か「DFSをフルで回してなるべく長い経路を作るのは2秒じゃ無理」と思い込んでいました。その思い込みの結果、「毎ターン深さを20以下に絞ったDFS→ドン詰まったら雑に10手戻ってやり直す」という今思うとだいぶ愚かな解法に走ります。この解法でも一部seedでは4万点近く出ますが、100ケース回すと2.6M点に留まります。ダメそう...。

 

6日目 12月7日(水)

 AHC廃人の朝は早い。午前4時に目覚めた藤川は、慣れた手付きでi3wmを操作し、Discordのチームチャットを開いた。その時、藤川に衝撃が走った...!

 チームメイトのスコティさんがシンプルなDFSで4.2M点を叩き出していました!!お...俺は何故これじゃ間に合わないと思いこんでいたんだ.....!?

 さらにスコティさんは上下左右の優先度設定次第で大きくスコアが変わることも突き止めていて、全24通り試して結果をまとめてくれていました!神!GOD!スコティ神社建てます!!

 スコティさんのC++コードをそのまま私の愛用言語であるCrystalに移植して提出してみたところ、スコティさんよりちょっぴり高いスコアを出せてチーム最高得点の座を頂きました。実は私、前回の皆解会001でもチームメイトの解法丸パクリでチーム最高得点を頂くというカッコ悪いことをしてたんですよね。ふじかわひろあきは長いですから、他人の褌で相撲取りerとお呼びください。※このネタ自体がながたかなさんのパクリというハイコンテクストなネタです。

 さて、スコアが高くなる移動パターンを見てみるとURLDやLDURなど、「上下どちらか 左右どちらか 左右どちらか 上下どちらか」または「左右どちらか 上下どちらか 上下どちらか 左右どちらか」であることが分かりました。この並びになるのは全8パターンしかありません。またDFSに大して時間がかからないので8パターン全て試して一番良いものを選ぶ方式でも余裕で間に合うことも分かりました。このやり方でスコアが一気に4.6M点までアップ!!!!

 個人順位は31人中5位、チーム順位はビリから4位まで上がりました。この時点での最下位は「焼きにゃまし隊」になりました。ふはははは!!ワシらの勝ちじゃ!!やーーいビリ!!!!

6日目ランキングその1

 ......調子に乗っていたのも束の間、なんとこの日の夜までに焼きにゃまし隊は一気にスコアを5.3Mまで上げて3位にのし上がります。ブチギレた私はランキング表管理者のあべみさんにお願いしてチーム名を一時的に「焼きにゃまし隊を焼き払い隊」に変えてもらいました。

 

焼きにゃまし隊を焼き払い隊

7日目 12月8日(木)

 朝起きたら個人順位が5位から10位まで落ちていました。は!?!?

 そろそろDFSの限界に来ている感じがするので、いよいよメタヒューリスティックなテクニックをブチ込んでいきたいところです。

 今回の問題には以下の性質があります。

  • 貪欲法(広義)でそこそこのスコアが取れる
  • 過去の経路が邪魔になる

 これ、AHC014(estieプログラミングコンテスト2022)に似てるんですよね。このコンテストの上位陣は過去の経路の一部を破壊して再構築していました。

 似たような解法がAHC002にも使えるんじゃないか?そう思った私は、最終的に焼きなまし法に切り替えることを前提に、まずDFS初期解の一部を破壊して再構築する山登り法を実装してみることにしました。

 手順は以下の通り

  • 比較的近めの2点をランダムに選ぶ
  • 初期解とは別ルートでその2点を繋ぐ
  • スコアが上がるなら新ルートを採用(山登りなので)

 理屈は単純ですが、いざやってみると無限にバグが生まれますね。22時までかかってようやく完成しましたが、スコアは微増程度(4.6Mから4.7Mに上がった)であまり効果がありません。※後から分かったことですが、この時点では盤面の状態管理にバグがあり、山登りの効果が十分に発揮できていませんでした。

 山登りだけだとこんなもんか〜と思いながら、いよいよ焼きなまし法へのチャレンジが始まりました。私はこの時点では一度もマトモに焼きなまし法を使ったことがなく、マジのド素人です。

 以下の2つの神記事を参考にしながら焼きなまし法を実装するも、スコアは下がっていくばかり...。この時点では温度が下がっても遷移確率が落ちないバグと格闘していました。

誰でもできる焼きなまし法 - gasin’s blog

焼きなまし法のコツ Ver. 1.3 - じじいのプログラミング

 結局スコアを上げられないまま眠りに就きました。

 

8日目 12月9日(金)

 rinrionさんのアドバイスも参考にしながらようやく遷移頻度がちゃんと下がるようになったものの、やはりうまく焼けません。いったい何がダメなんだ!?温度関数か!?遷移確率関数か!?

 この日は何の成果も無く終わりました。気付けば我がチームは最下位で、5Mの壁を越えていない唯一のチームとなりました...。悔しい...。

 

9日目 12月10日(土)

 朝起きたら焼きにゃまし隊のひらさんが「なんだこの集まり、レベル高すぎる」というツイートをしていました。ツイートには順位表のスクショが貼られていましたが、我ら三尺秋水だけ見切れています。これは完全に煽りですね(# ゚Д゚) 焼きにゃまし隊、ぜってー全員焼き払ってやる!!!!

 焼きにゃまし隊への敵愾心を燃やしながらパソコンに向かいます。これまで焼けない原因は温度関数または遷移確率関数にあると思い込んでいましたが、そもそも山登りによるスコアの上げ幅が小さすぎるということは、盤面の状態管理や更新の方にバグがあるんじゃないかって気がしてきました。

 早速盤面の状態を確認してみたところ、実際には通っていないマスも通っていることになってしまっていました。これじゃあうまくいかないのも納得です。

 焼きなましの試行回数を稼ぐために盤面のディープコピーという重い操作を避けるようにしていたのですが、まずはバグ取りを優先して、再構築DFS時に毎回盤面をディープコピーするようにしてみました。

 これでちゃんとバグが取れることを確認し、seed = 0を放り込んでみたらしっかり焼けている!ローカルテスト100ケース回しても全seedスコア爆上げしている!ちゃんと焼けてる!!いける!!いけるぞ!!いざ提出!!

 それまで4.7M台だったスコアが一気に5.8Mまで爆上がりしました!!!!うおおおおお!!!!!!!

 焼けた!!ついに焼けたよ!!!!!私でも焼けるんだ!!!!!!!!!

 個人順位表を見てみると、私を煽ったひらさんをしっかり抜いていますね。ふははははは!!

ひらさんを抜いた

 しばらく喜びに浸っていましたが、まだチーム最下位なことに変わりはありません。さらに工夫してもっとスコアを上げねば...。

 

10日目(最終日) 12月11日(日)

 ついに最終日。なんとかして最下位を脱出せねば!!

 温度関数や遷移確率関数や破壊する範囲を調整してみても大きな変化はありませんでした。一方で焼く時間を制限時間より長くしてみたところ、大幅なスコア向上が見られました。パラメータ調整や破壊・再構築方法の工夫より高速化の方が先決っぽいですね。

 焼きなましの試行回数を調べてみたところ、わずか2万回程度しか回っていないことが分かりました。そりゃ弱いわ。

 試行回数が少なくなっている原因の心当たりは以下の3つです。

  • 毎ターン開始時に盤面を全部更新している
  • 経路の一部を破壊する時に、操作列全部に対してループしている
  • 経路再構築DFS時に盤面を何度もディープコピーしている

 上記2つは比較的簡単に改善でき、試行回数は6万回程度まで上がり、スコアは5.9M台にギリギリ届くようになりました。

 しかし盤面ディープコピー問題はうまい解決策が思いつきません。マクロを使った黒魔術など色々迷走しましたがうまくいかず、気付けばコンテスト終了5分前。

 もう終わったか...... そう思った時、ようやく有効な改善策が思いつきます。

 DFSの1探索が終わる度に探索した分だけループして盤面を元に戻す。一度に破壊・再構築する範囲は小さいので、このループによる計算量の増加はディープコピーのコストよりは遥かに実行速度への悪影響が小さいはずです。

 実装してみたところ、試行回数は1ケタ増えて15万回程度まで上がりました!!ローカルテスト100ケース回したところ5.9M台後半〜6Mギリギリくらい!!これはイケる!!!!.............あれ、19時5分じゃん、コンテスト終わってるじゃん(´;ω;`)(´;ω;`)(´;ω;`)(´;ω;`)(´;ω;`)(´;ω;`)

 あの時Twitterを眺めていなかったら、あの時おやつをのんびり食べていなければ、あの時ちょっぴりお昼寝をしていなければ....私のベスト提出はタラレバワールドに消えていきました.......。

 長渕剛「ろくなもんじゃねえ」を聴きながらモニターに向かって「ろくなもんじゃねえ...」と呟き、10日間にわたる私たちの戦いは終わったのでした.......。

 チーム最下位、個人では30人中9位でした。コンテスト後のainemさんローカルシステスでも順位は動きませんでしたが、ついでに涙の5分後提出もテストしてもらったところ、これが間に合っていれば最下位を回避して5位になれたという悔しすぎる事実が発覚しました.............。ううっ!!タラ!!レバ!!アンドろくなもんじゃねえ!!!!!!!

おわりに

 順位的には悔しい結果になってしまいましたが、初めて焼きなまし法を使うことができて非常に学びの多いバチャとなりました。主催のainemさん、チームメイト、そして敵チームの皆さんに感謝です。

 焼きなまし法やビームサーチといったメタヒューリスティックなテクニックに対して今まで何となく敷居の高さを感じていたのですが、いざ使ってみるとそれ自体は結構単純な理屈であり怖がる必要は無いことが分かりました。

 しかし同時に、どうやって焼ける形に持ち込むかを考えることと状態の持ち方や遷移の仕方を工夫して高速化することが非常に難しいことも分かりました。

 今回は本来4時間のコンテストを10日かけてチームメイトと相談しながら解くことで本番の上位陣に匹敵するスコアを叩き出すことができましたが、4時間だったら何もできずに終わっていました。私が10日かけて必死で稼いだのと同等またはそれ以上のスコアを僅か4時間で独力で叩き出す強者たちの凄まじさと彼らとの圧倒的な差を痛感しました...。

 このバチャを通してようやくヒューリスティックコンテストのスタートラインに立てた気がします。AHCはとても奥が深い最高のネトゲです。私としてはアルゴコンよりかなり面白く感じます。今後も皆解会で修行しながら強くなってやるぞい!!!!永遠に茶下位で停滞してるアルゴコンの方もちゃんと精進しろって?あ、あの、気が向いたらやります、はい....

AHC016(HTTF2023予選)敗戦記 〜傷まみれの青春〜

はじめに

 AHC016 (HACK TO THE FUTURE 2023予選)お疲れ様でした。AtCoderアルゴ茶永遠停滞 & ヒューリスティック緑の冴えない競プロer 藤川です。

 

 本記事をご覧になられる方は大体コンテストに参加されていると思うので、問題の説明は省略しますが、今回も大変面白い問題でしたね。

 お陰さまで10日間とても楽しく苦しめましたが、結果は342位(パフォ1458)とイマイチで非常に悔しいです。完全に"""負け"""だと思っています。

 本記事はそんな敗北者による敗北の記録です。解法は非常に単純でわざわざ記事を書くほどのものではないのですが、誤った思考による敗北の一例としてご笑覧いただければ幸いです。

 なお本記事は解説記事ではなく、あくまで参加記です。解法についての説明は最小限でほとんどただの日記であることを予めご理解ください。

 

 さて本編に入る前に、まずは本記事のテーマソングである長渕剛「傷まみれの青春」を聴いて頂きます。

 

youtu.be

 OK、聴き終わりましたでしょうか?現在の私の心境がなんとなくお分かり頂けたと思います。それでは本編に入っていきましょう。

 

初日 絶望

 問題が難しすぎてひっくり返っていました

 大半のテストケースにおいて辺の有無が結構な確率で反転する上に、頂点の順番がシャッフルされちゃうだと!?しかもそんな状況でなるべく少ない頂点でグラフを表現しなきゃいけないだって!?オマケに相対評価方式だからε小さめで簡単なケースで多少稼いだところで無に帰しちゃうっぽい!?!?

 こんなんどうやっても無理では???ふええ........

 絶望感と高橋くんへの若干の怒りを抑えながら、とりあえずサンプルのPythonコードを私の愛用言語であるCrystalで書き直して正の得点を得てから寝ました。

 

2日目 まじ無理

 まずはサンプルコードをちょっとだけいじって、0〜M - 1までのグラフ番号と同じ数の辺を1にしたグラフを作るだけの単純なコードを書きました。この時点での絶対スコアは約1.1億点。

 その後はひたすら問題文と睨めっこしながらウンウン唸っていました。いやマジでムズすぎ。

 

3日目 くわがたかっこいー!!

 虫オタク仲間の集まりがあったのでデカいクワガタをたくさん見れました。くわがたかっこいーーーー!!!!!!!!!!!ぼくタランドゥスオオツヤクワガタだいすき!!!!!!!!!!!!!!!!(AHC?何の成果も得られませんでした)

 

4日目 実験

 考察以前にまずグラフを01文字列で表すことへの理解を深めようと思い、色々なグラフを01文字列化してみては各頂点ごとに分割したりシャッフルと01反転のシミュレーションをしたりしていました。

 

5日目 スタートライン

 この日はド平日。いつものように山の中で泥にまみれながら超単純な肉体労働をこなしていました。

 あまりにも単調な仕事なので脳内は暇で暇で仕方ありません。退屈しのぎに問題のことを考えていると、ようやく有望な策が浮かんできました。

 

 「例えば辺の数が100でεが0.1のグラフのうち20本の辺が1で80本が0の場合、エラー発生後の1の数は20 × 0.9 + 80 × 0.1 = 26本前後になるよな!?んでこの推定値って多少のズレはあっても他のグラフとあんまり被らないよな!?使えるじゃん!!!!」

 

  1. まずは各εに対して十分な数の「1の塊」のサイズを決めます(とりあえず雑にε × 1000にしておく)。ここではカタマリと呼ぶことにします。
  2. グラフ番号 × カタマリの数だけ1、残りは0のグラフをM個用意します。
  3. 辺が反転された後の1の本数の期待値(って言葉選び間違ってないよね?)を求め、H(変化後のグラフ)の1の本数との差を見ます。差が最も小さいグラフが答えである可能性が高いです。

 

 εもMも大きいケースではNを100にしても十分なサイズのカタマリを作れず前後4つくらいのグラフと期待値からの変異幅が被ったりしちゃいそうですが、とりえあずMが小さいケースならなんとかなりそうです。

 ワクワクしながら帰宅してサクッと実装して提出してみたところ、さっきまで1.1億点程度だった絶対スコアが一気に3.5億点まで跳ね上がり、暫定380位から一気に194位まで上がりました!!!!うおおおおおお!!!!!!!!!!

5日目 ピーク

 手元で回した100ケースでは、Mが小さくεに対して十分なサイズのカタマリを作れたケースでは大体エラー数が1ケタまで落ちていました。やったね!!!!!!!!!

 めちゃくちゃ喜んでしまいましたが、こんなごく単純なことに皆が気付かないワケがありません。これで私はようやくスタートラインに立てただけです。走り続けなければあっという間に全人類に抜かされるでしょう。走り続けなければ........。

 

6〜9日目 停滞

 走り続けなければ...........。そう思いながらも、なかなか追加のアイデアが浮かんできません。見る度に落ちていく順位表、焦りが募っていきます。

 

 今後の大まかな方針として2つの案があります。もちろんどちらも出来た方が強いですが、私の能力では残りの日数でできるのはどちらかのみだと思われるので、片方に集中するしかなさそうです。

  1. εが小さいケースでは少ない頂点数で十分に固有なグラフを作れるはずなので、事前に必勝パターンを用意しておきスコアを荒稼ぎする
  2. εが大きいケースでのエラー率を丁寧に下げていく

 直接的にスコアに結びつきそうなのは1の案です。εが大きいケースでNを100近くにして現状の解法をベースに小細工して多少エラー率を下げたところでまあ焼け石に水でしょう。難易度的にも1の方が遥かに楽そうです。

 しかしここで1つの不安が頭をよぎります。そう、相対評価システムです。みんなが高得点取れるケースでスコア稼いだところで1位の人にバシーーン!!と割られてしまいます。それならいっそのことイチかバチかεが大きいケースをゴニョゴニョと頑張ってみた方が希望があるのでは..........。難しそうだけどやってみるか........................。

 

 そして私は愚かにも地獄の扉を開けてしまったのでした。

 

 山中の肉体労働現場で昼休みにパソコンを開き、皆がお昼寝する中で一人HHKBを叩いても答えは出ず......。

山奥肉体労働現場AHC

 海を眺めながらウンウン唸っても答えは出ず...........。

昼休みシーサイドAHC

 がむしゃらに走って汗を流しても答えは出ず.............。

ランニングという名のAHC考察タイム

 完全に煮詰まってしまいましたね...。

 

 6日目〜9日目の四日間、皆がどんどんスコアを上げていく中で私はひとり停滞、いや後退し続けていたのでした...........................。

 

10日目 狐の最後っ屁

 一時は194位まで上がっていた順位もすっかり落ちてしまい、気付けば380位...。期待値作戦による順位爆上げ以前のところまで戻ってきてしまいました...。

 少ない残り時間、浮かばない打開策。

 追い詰められた私は終了2時間前になってようやく自分でもできそうな簡単な改善策を思いつきました。

 理屈は非常にシンプルです。

  • εに対して十分なカタマリを作れないケースの場合、カタマリのサイズを2倍にする
  • 同じカタマリサイズのグラフを交互に作り、片方は先頭から1を詰め、片方は全体に1を分散させる
  • 先頭から1を詰めたグラフ = より多くの辺を持つ頂点では、01反転 & 頂点シャッフル後も当然ながら1が連続しやすいことを利用する
  • 事前にシャッフルと01反転のランダムシミュレーションを各グラフに対して同じ回数ぶん回し、各シミュレーション結果における1の最大連続数を配列に格納していく
  • まずは1の数の期待値で2択に絞り、変化後のグラフにおける1の最大連続数が最も多く含まれる配列を有しているグラフの方を答えとする
  • カタマリサイズが辺の数の半分を超えたら0と1の役割を逆転し、0を前から詰める(保有する辺が少ない後半の頂点に跨って1を配置してしまうことでシャッフルの影響を受けやすくしてしまうことを防ぐため)

 実装してみたところ、それまでエラーが70〜90台程度あったεの大きいケースの大半で40以下まで落とすことができました。Mが小さければ20以下や1ケタにまで落ちるケースもありましたね。例えばcase76(M = 13, ε = 0.39)ではエラーを15まで落とせています。

case76 結果

 

case76グラフ推定成功

 ちゃんと効果はあったようですが、Nが大体100近くあるのでスコア的には焼け石に水です。ローカルテスト100ケースではそれまで8.1億点だったのが8.4億点になった程度...。劇的な改善にはならなさそうですが、もう時間も無いですし解法の改善はこれでおしまいにして、いったん提出してTLE等が起きないか確認してからパラメータ調整に入りましょう。

 コンテスト終了50分前に提出してみたところ、案の定TLEが起きてしまいました。実行時間ベースではなく回数ベースでのシミュレーションだと調整が少々難しいですね。

 もう時間が無いのでシミュレーション回数を無難な回数まで減らした上でお気持ちパラメータ調整を施し、終了10分前に最後の提出を済ませました。今度は全ケース無事ACですね。

 この改善の結果、380位から342位まで上がりました。地味ですがやはり多少なりとも効果はあったようです。いや、地味ですが...。

 こうして10日間におよんだ戦いは何とも地味な終わり方をしたのでした..........。

 

悲しみの感想戦

 さてコンテストが終われば恒例のTwitter感想戦タイムです。

 薄々勘付いてはいましたが、やはり皆さんεが小さいケースでは必勝グラフを用意していましたね。確かに相対評価時の減点幅がデカいとはいえ、そもそも楽なケースでNを減らして荒稼ぎするのは最低限必要なラインだったようです。私はスタートラインにすら立てていなかったのか........!!!!!!

 その上で皆さんクリーク(なにそれ食べれゆの!?)と補グラフ(なにそれ食べれゆの!?)を活用してεやや大きめ〜大きめのグラフに対処していたようですね。もうね、比較になりませんよ、私のよゎよゎ解法。

 AHC013や014では私と良い勝負をしていた方々が今回は私より圧倒的に上の順位にいて悲しみが深まります。うっうっうっ.........................。

 

システス

 コンテスト終了後のシステスで興味深い現象が見られました。相対評価が反映される前に一時的に絶対スコアが表示されていたのですが、絶対スコアでソートしてみるとプレテストの相対評価順位との乖離がかなり大きかったんですよね。私は同順位帯の中では絶対スコアがやや低い方だったようです。εが大きなケースの対策にリソースを割いていたので当然といえば当然ですが、これ相対評価じゃなかったらヤバかったな...と肝を冷やしました。

 コンテスト終了から二日経ち、システスにも相対評価が反映され順位が確定しました。プレテストと変わらず342位。うーーーーん、340位の賞金ニアミス!くやしい〜〜〜〜〜!!!!!

 パフォーマンスは1458で一応水パフォ後半、レーティングは1046から1147に増えました。

 

AHC016結果

 水パフォといっても、参加登録だけしてサンプルすら提出していない事実上の不参加勢も含めての比率で算出されたパフォなので、1回以上提出している真の参加者内(1100人程度)でのパフォは実質茶パフォ後半程度と思われます。

 これは完全に"""負け"""でしょう.....。負けた........。俺は...負けた................。

 

反省

 さて今回の敗因をまとめると以下の通りです。

  • εが小さいケースでN減らしを頑張ったところでどうせトップの提出で割られて無意味だと早々に決めつけてしまった
  • エラー率を下げることに躍起になりすぎてNを下げることを軽視してしまった
  • 相対評価システムを過度に警戒し、ε大きめケースの対処という自分の能力を超えた方針を無理に選んで時間を無駄にしてしまった
  • ゆーてみんなε小さめケースのハックそんなに本気出さんやろwとナメてかかっていた

 大体が誤解と思い込みによるものですね...。方針を固める前にまずは簡素な改善を複数パターン実験的に提出し相対評価の動きを見て判断すれば良かったものを......。

 

 私の頭の中には今、「傷まみれの青春」が流れています。

 

 泣けて泣けて泣けるんだよ どうしようもないから

 誤解・偏見・独断の あんちきしょう 傷まみれの青春(コンテスト)

 (長渕剛, 1996)

 

AHCガチ素人のアルゴ茶コーダー VS AHC013 〜初歩的アルゴ知識だけで戦ってみました〜

はじめに

 AtCoder Heuristic Contest それは「絶対の正解がある問題をなるべく早くなるべく多く解く」勝負であるアルゴリズムコンテストとは毛色が異なり、AtCoder公式の言葉を借りれば「最適解を出すことが難しい問題に対し、出来るだけ良い解を作成する」勝負になります。

 上位を目指すにはアルゴコンでは馴染みの無い「焼きなまし法」や「ビームサーチ」などの手法が必要になりますが、残念ながら現時点で私はそれらについて理解できておらず、コンテストで使えるレベルには程遠いです。

 いつかはそれらの手法も使いこなせるようになりたいところですが、今回のAHC013(AtCoder Heuristic Contest 013 RECRUIT 日本橋ハーフマラソン 2022夏)にはとりあえず今知っていること・今できることだけで取り組むしかありません。

 というわけで、AHCガチ素人のしがないアルゴ茶コーダーが、なけなしの初歩的なアルゴリズム知識だけでAHC013に挑んでみました。

 

初日

 参加登録して問題文を眺めながらビール飲んでました。それ以上のことは何もしていません。まあね、長丁場だからね、初日くらいのんびりしたっていいじゃないか。人間だもの。

AHC013初日ビール

 

2日目 まずは正の得点

 とりあえずサンプルコードのPython版を私の愛用言語であるCrystalでそのまま書き直して正の得点を得ました。29579点。

 Python版のサンプルコードが用意されていたのは大変ありがたかったですね。C++やRustのコードをCrystalで書き直すのは少々面倒なのですが、Pythonからの移植は容易なので。

 そしてビジュアライザを眺めてみました。

 まずは最初に表示されているseed = 0から見てみましょう。ゲッ!?何この移動しづらそうな高密度!?......まあ落ち着け、こういうのはとりあえず真ん中を見てみるもんだ。というわけでseed = 50を見てみると空白セルだらけで一安心。その後seed = 100を見た上で他も適当にいくつかチェックした結果、たいていの盤面では空白セルが多いことが分かりました。とりあえずは移動が簡単そうな空白セル多め盤面をメインに考えておけばよさそうです。

3日目〜4日目 停滞期

 この時点ではまだ問題についてあまり深く考えていませんでした。

 とりあえず、サンプルコードで移動と接続が同じクラスに属してセルの状態を共有していたのが今後の改善の際にバグの温床になりそうだったので、個別の関数にバラしておきました。あとはコンピュータの種数(k)行置きに各コンピュータ専用の行を決めて、深さを5以下に限定して幅が爆発しないように抑えたBFSで移動させる(なおすぐに移動回数を使い果たしてしまい中途半端)などのあまり意味の無い小手先の改造を移動部分に戯れ程度に施しました。接続についてはサンプルコードのまんまで何もイジっていません。

 当然、この程度で大きくスコアが伸びることはなく、53471点止まりでした。

 この時点でのseed = 50, k = 5でのスコアはわずか510でした...。

3日目時点でのseed = 50, k = 5

5日目 爆発

 前夜のABCで(お昼に飲んだ牛乳による腹痛で何度もトイレに駆け込むなど色々トラブルがあったとはいえ)初参戦以来のA1完という凄まじい爆死を喫し、レートを一気に55も下げて、皆様のレートの養分となり"""徳"""を積んだ私に競プロの神様からAHCの天啓が下る!...なんて甘い話があるわけもなく、己のあまり出来のよろしくない脳みそで普通に悩んでいました。

 しかしモチベーションは前日までとは比べ物にならないほど高くなっていました。昨夜のABCの屈辱は絶対にAHCで晴らしてやる。今に見てろ。

 

 本気でスコアを上げにいくにはまず問題文を冷静に見ることが先決ですね。初日にビールを飲みながら漫然と眺めていただけの問題文と改めて向き合ってみました。

 

 まずはスコア計算法をよく見てみます。同じクラスタに属する同じコンピュータの組み合わせの数によってスコアが上がるようですね。合計ではなく組み合わせによってスコアが上がるということは、全てのコンピュータを満遍なく繋げるより1種でもいいからなるべくデカいクラスタを作った方が良いっぽいことが分かります。

 組み合わせ、そう、すぐに計算量爆発しちゃって世のプログラマーたちを苦しめてきたあの組み合わせです。でも今回はどうも味方にできるっぽいです。組み合わせ問題で計算量が爆発するってコトは、この問題の点数も爆発するってコト...?(ちいかわボイス)

 組み合わせといえばnCrです。競プロ始めるまで知らなかった(もしくは高校あたりで習ったとしても覚えていなかった)あのnCrです。ググって出てきたnCr計算サイトに100C2を計算してもらったところ、4950という答えが帰ってきました。1種のコンピュータを全部(100個)繋げることができれば1ケースあたり4950点取れるということです。ほほーーーん。

 

 次に制約を見てみましょう。移動と接続の合計がコンピュータの種数×100回までしかできないとされていますね。同種のコンピュータを100個全部繋げる場合、接続だけで1種につき最小でも99回分消費してしまいます。コンピュータの種数の制約が2以上5以下であることから、めちゃくちゃ効率的に最小限の移動だけで理想の盤面を作れたとしてもせいぜい2種分繋げるのが精一杯っぽいです。

 

 以上をまとめると、この問題の目標は「2種のコンピュータで全域木を作成すること」だと推測できます。 

 

 しかし移動回数と接続回数についての厳しい制約の中で全域木を2種分作れるような移動なんて、私にはちょっと難しそうです。でも1種分ならある程度なんとかなる気がします。

 というわけで、自分の実力の範囲内で出来そうな現実的な方針として「1種のコンピュータについて可能な限り全域木に近いものを作り、余った接続回数で他種も接続」することにしました。

 

 いざ実装!!まずは全種について専用の行を定めていた移動部分を変更して、全域木ターゲットとなる種だけ専用行を定めることで移動回数を節約しつつ全域木を作りやすくしておき、さらに接続部分をイジってコンピュータを1種だけ可能な限り長く接続するようにしてみました。接続方法自体はとりあえずサンプルコードのまんま、右と下に貪欲に接続する方式です。接続を1種に絞った場合のスコアの伸び具合を確認したいので、余った接続回数については一旦放置しておきます。

 出力をビジュアライザに放り込んでみると、さっきまで510点だったseed = 50, k = 5が一気に4187点になりました!!!!文字通り桁が違うぜ!!!!!!!!!これはイケる気がする!!!!!!!!!!

4日目、全域木作成作戦に切り替えた直後のseed = 50, k = 5

 ドキドキしながら提出........結果はなんと142057点!!!!!!さっきまで5万点台だったのが一気に14万点台!!!!!!!!!まさしく爆発!!!!!!!!!!!

 

5万点から一気に14万点、爆発の瞬間

 この瞬間、私はたぶん夜神月の「計画通り」顔になっていたと思います。(知らない人はググってみてね)

 

 ここ最近では最高クラスの脳汁噴出タイムを味わい大満足...と言いたいところですが、ここで手を緩めれば次々に抜かされていくのが競技プログラミングというものです。なんたって今回のAHC013の別名は「RECRUIT 日本橋ハーフマラソン2022夏」ですからね。常に走り続けなければなりません。

 

 この日の夜までに以下の改善を施し、170719点まで伸ばしました。朝の時点で5万点台だったのが17万点台!!!!!!!約3倍ってマジ!?!?!?!?!?

  • 余りの接続回数をターゲット種以外のコンピュータで使う
  • ターゲットとなる種をループで入れ替えていき、最もスコアが高くなるものを採用
  • 現状の移動方式が通用しない空白セルの少ない盤面については、サンプルコードの乱択方式にスコア計算を加えて、スコアが大きくなる移動のみ採用...あれ???これと前項のターゲット種変更作戦を合わせると、実質やってることは「多出発山登り法」じゃね?????この記事の看板「初歩的アルゴ知識だけで戦ってみました」に偽りありじゃね?????(この記事書いてる時に気付きました)まあこまけーこたぁ気にしないでくれ!!

17万点超え

 周りに水色青色が多いのがなんだか嬉しいですね。私、けっこう戦えてんじゃん、えへへ。

 お盆休み最終日にしてようやくある程度納得のいく成果を出せました。間に合った........。

6日目 再び停滞

 さてお盆休みが終わり、本日から憎き平日です。お仕事のスキマ時間をうまいこと使っていくしかありません。お昼休みをAHCに全betし、仕事仲間との会話や仕事のパフォを上げるためのお昼寝はナシ!!大人として何か間違ってる気がしますが、まあAHC期間中だけは許してくれ。

 しかしお昼休みを生贄に捧げたものの、本日はなかなか良いアイデアが浮かんできません。血迷って焼きなまし法についてググりまくった末に「うーーーん、この問題に焼きなましを適用するのは天才じゃなきゃ無理!w」などとしょーもない結論に至っていました...。

 この日は点数の向上はナシ。再び迎えた停滞期...。泣いても笑っても明日が最終日なのですが........。焦燥感を抑えながら眠りにつきました。

 

7日目 最終決戦

 来ちゃいましたよ、最終日が...。

 もう時間が無いので大幅な方針転換は諦めて、現状の解法をベースに小さな改善を積み重ねて少しでもスコアを上げていくしかないです。

 

 まずは接続方法の見直しから。現状ではサンプルコードのまんま右と下に貪欲に繋いでいく方式なのですが、これでは既に同一クラスタに属しているセルと再接続してしまって貴重な操作回数を浪費したり、何ヶ所か格子状に繋がってしまったり、無駄に長い辺を錬成してしまってターゲット種以外の接続を阻害してしまいます。

 なるべく最小限の接続だけで、格子状になるのを避けて、辺の長さもなるべく最小で、ターゲット種の全域木を作成したい。最小限の全域木...最小...全域...最小全域木!!!!!

 しかし悲しいかな、私はか弱いアルゴ茶コーダー。最小全域木をパッと作れるほどの能力なぞ持ち合わせておりません。でも大丈夫、この世界にはGoogleがある!!

 偉大なる師・マスターGoogleに教えを乞うたところ、最小全域木を作るにはプリム法とクラスカル法という2つの道があるそうな。そういやすっかり忘れてましたが、プリム法についてはPAST本で履修済みですね。じゃあクラスカル法で!!

 もう時間が無いのに未履修アルゴリズムを選んだ理由?知らないアルゴリズムでやった方が楽しそうですし、何よりクラスカル法の方がなんとなく名前がカッコいいから!!!!(そろそろ読者の皆様お気づきかもしれませんが、私は結構なバカです)

 いざ実装。辺が交差できないというルールに注意が必要です。まずはサンプルコードから拝借した接続ループ中で実際の接続や使用済みセルの登録をしていたのをやめて、辺とコスト(接続するセル間の距離)を配列にぶち込んでいきます。全セル見終わったら辺とコストを入れた配列をコストの昇順にソートし、伝家の宝刀UnionFindにぶちこみながらループを回して、接続セル間の空白セルたちを使用済みセル集合に放り込み、UnionFindを利用して既に同一クラスタに含まれているセル同士の接続はスキップしながら繋いでいきます。ほい完成。様々な縛りの中で作ったので完全な最小全域木ではないものの、ビジュアライザを見比べると明らかに無駄な接続が減っていますね。

 まずはビフォー 

最小全域木もどき導入前

 そしてアフター 

最小全域木もどき導入後

 

 これだけではスコアは僅かしか向上しません。無駄な接続を減らしたところで、そもそも接続可能な配置になっているセルが少ないことに変わりはないからです。でも無駄な改善ではありません。接続回数を節約したことで、移動回数に回すことができるからです。

 節約した接続回数をk = 4以上の場合は移動用に回すことにします。これまでサンプルコードのまんま移動回数と接続回数を半々に割っていたところ、k = 4なら250回、k = 5なら300回を移動用にします。増やした移動回数を使って、今まで行単位でしか指定していなかったターゲット種の位置をループ二周目で列単位でも指定するように変更しました。

 提出してみると183958点!!!!ようやく18万の壁を超えたぜ!!!!!!

 

 気づけばもうタイムリミットが目前。この後はせいぜい乱択移動モードにする基準をいじってローカルテスト100ケースで一番良い成績を出した基準値に変更した程度(なお提出するとさっきより少し下がって183829点、乱択の変異内であまり意味の無い変更だったようです)で、大きな改善を施すことなくコンテスト終了時刻を迎えました。

 

 終了直後の暫定順位はまさかの200位ピッタリ。参加人数から考えてパフォーマンスはおそらく青パフォと水パフォの境目くらいでしょうか。

 200位!?え!?このままいけば200位の飛び賞で景品もらえる!?!?などと色めきたちましたが、「国内在住の景品対象者かつ景品希望者のみの順位で受賞者が決まる」仕組み上、ちょっと厳しそうですね.......。

 

 終了後はTwitterでおなじみの感想戦。今回の私の予想外の健闘を褒めてくださる方も何人かおられて、お調子者の私は若干浮かれ気味に終戦後のTLを楽しんだのでした。

 

終戦翌日

 システス後も私の順位は変わらず200位。パフォーマンスは惜しくも青パフォ (1600以上)に届かず、1595で水パフォのほぼ上限スレスレ。

 

AHC013 成績証

 無提出者が多い上にまだ競技としてアルゴほど成熟していない感のあるAHCでは、単純な乱択虚無解を提出するだけで緑パフォが出たりするので、アルゴの同一パフォに比べると価値が低いのかもしれませんが、それでもまあ水と青の境目はかなり頑張ったと言えるのではないでしょうか(ドヤ!!

 そしてレートはなんとをすっ飛ばして一気に入緑!!!!!!倍以上に増えたぞ!!!!!!

 AHCはアルゴとは異なりレートが下がらない仕組みなので、私は永遠にAHCで入茶はできないことになります。なんて嬉しい機会損失。

一気に入緑

 国内在住者の人数を確認するため順位表検索で日本人に絞ったところ188位。海外在住日本人や国内在住外国人による若干のブレがあるとは思いますが、自分より上位に15人前後景品を希望しない方がおられたら170位の飛び賞はワンチャンあるってことですね。なんとかなれーーーーー!!!!!!

 

おわりに

 今までなんとなく参加しては何もできずただ正の得点を得るだけの虚無解を出して終わっていただけのAHCで、今回は自分でもびっくりするくらい戦えました。ようやくAHCの楽しさがちょっと分かった気がします。これはハマっちゃいそう。

 

 最後に今回のAHCでの良かった点と悪かった点と雑感を挙げて、本記事のシメとします。

 

良かった点
  • スコア計算と制約から適切なゴール設定を導出できた
  • 迷走した6日目を除けば現実的に自分でも実装できる範囲の解法を無理なく選択できた
  • クラスカル法(もどき)の実装を通して今までぶっちゃけよく知らなかったUnionFindへの理解が深まった
  • 「UnionFind?なにそれ食べれゆの?」から「UnionFindおいしい!!」になった
悪かった点
  • 各seedの確認が不十分で、木が十分に大きくならないケースへの対応が甘かった
  • 密な盤面での多出発山登り法もどきの改善をほとんど行わず、サンプルの重いスコア計算をそのまま使い続けていた
  • スコア計算と制約をきちんと見るのが遅すぎた(初日か2日目の時点で適切なゴール設定ができていればもっと伸ばせたはず...)
  • 色々やりたいこと・やるべきことが他にあったはずのお盆休みを完膚なきまでに破壊し尽くしてしまった

 

雑感
  • AHC序盤における適切なゴール設定は、制約をよく見てパズル的な思考を働かせるというARCっぽい考え方が必要なのかもしれないと思った
  • 今回はたまたま焼きなましなどのテクニックが(上位の神々の争いレベルを除けば)あまり必要ではなく貪欲系なアプローチが有効だったからこそ私でもある程度なんとなかっただけで、今後のことを考えるとやはり焼きなましやビームサーチは学習した方が良さそう
  • AHC期間中に他のやりたいこと・やるべきことができると思ったら大間違いだぜ!!!!!事前に済ませような!!!!!!!!!

 

 次回のAHCは9月下旬ですね。次はもっと勝ってやらあ!!!!!!!!!!!

 お読み頂いた皆様、ありがとうございました!!ではでは(・ω・)ノシ