MC Digital プログラミングコンテスト2024(AtCoder Heuristic Contest 031) 参加記

約3年ぶりに Heuristic のコンテストに参加しました。競技プログラミング自体ここ数年は離れていたのですが、春休みに元JOIerたちと会い、モチベが出てきていたところにTLでコンテストを見かけ、参加することにしました。
久々の参加ながら、今までで一番ちゃんと取り組んだので、記録を残しておきます。

終結

35位で、Ratingが1.5倍くらいになりました。

順位表(更新前のRating)

問題

atcoder.jp
大きな長方形のホールについて、たくさんのお客さんから「これ以上の広さの場所を割り当ててほしい」という要求が何日かにわたってたくさん来るので、それに沿うように各日について場所の割り当て方を決め、長方形を敷き詰めていく問題です。ペナルティは、要求された面積未満しか割り当てられなかったときと、長方形の仕切りを撤去・設置するときに生じます。仕切りの撤去・設置にコストがかかるのがこの問題特有で、前日で使った仕切りをいかに利用していくかが大事になります。

最終的な方針

atcoder.jp

  • 基本的に、行仕切りはほぼ固定で、左上から順に長方形を横に並べていきます。
  • 縦の長さ W=1000 を、長さ 153, 134, 125, 106, 93, 81, 72, 66, 53, 47, 38, 32 の行に分けます。なお、この値に論理的な裏付けは一切ありません。
  • 日ごとに、その日に発生するスコアを最小化する焼きなましをします。遷移は基本的には並べる順序の2点swapで、たまに1つの行仕切りの有無の切り替えが入ります。
  • 各日ではスコアが上位5位以内の解を保存し、次の日には5通りそれぞれの仕切りの初期配置について焼きなましをし、また上位5位以内を求める、というビームサーチをします。
seed=25, score=50596

コンテストの記録

コンテストは 3/22~4/1 の10日間で、その間で時間を見つけてやっていく感じでした。

3/26 (5日目)

コンテスト5日目の夜、ぼんやりツイッターを見ているとAHCをやっているらしいという情報が。軽い気持ちで参加登録し、問題を見てしばらく紙に書いて考えました。

長方形を敷き詰めるのに何も制約を付けないのは自由度が高すぎるので、一旦行仕切りを固定してみることにします。まずは扱いやすそうということで、2べきな感じの分け方をしました。1000=500+250+125+62+31+16+8+4+2+1+1 と分けました。 これだと行同士をくっ付けても上の行より幅が短くてうれしそうです。
そうすると、要求面積が大きい順に上から配置していきたくなります。配置しきれなかった分は調整が必要ですが、割と簡単に実装できました。
行の仕切りを付ける/付けないは2^10通りなので、そこは一旦全探索することとします。面積ペナルティが最小になる仕切り方を選びます。
これを出すと82488771点が取れます。400位/800人中 くらいになった覚えがあります。

seed=25, score=643955

ここまで一切仕切りペナルティを考えてこなかったので、同じ行の中だけでもうまく調節して前の仕切りを使いたくなります。前の仕切りと置く長方形の辺とをマッチングすることを考えると、DP(+経路復元)でそこそこ高速にできることがわかります。dp[i][j]: i個目まで置いて、j回マッチングできたとき、最小でどこまで埋まっているか とします。これで29395566点が取れ、点数が約36%になりました。

行ったDPは置く順序は動かさないやつでしたが、置く順序もbitDPで決められることに気づきます。その行に置く長方形の個数が小さい(18未満)のときbitDPをすると、25792122点が取れました。これで200位台くらいに入った覚えがあります。

seed=25, score=312958

ここで、「500,250,...ではなく別の分け方の方が良いケースがあるのでは?」と思い、200,200,200,...や300,230,170,...といった色々な分け方でこれまでのアルゴリズムを実行し、最も適した分け方で答えることにします。さらに、行仕切りの位置を少しずつ変えて評価する山登り法で分け方を微調整しようとしました。当然重いので、TLE。直そうにも提出頻度制限で結構待たされます。この時点で朝の5時になろうとしていたので眠ることに。ハマるといつまでもやってしまうのが恐ろしいところ......。

3/27(6日目)

起きた後、すぐに昨日のTLEを直したコードを出すと、15567836点が返ってきました。この時点でスコアが最初の提出の1/5くらいになっていて嬉しかったです。
ただ、これ以上の改善が思いつかなかったので、この日はこのまま手を付けずに寝かせました。

seed=25, score=250633

3/28(7日目)

ここまでの方針を思い返してみると、行の中で色々配置を変えてはいましたが、行を跨いだ変更は何もしていません。そこで、置いていく全体の順序そのものをいじっていく方針を試してみます。今の方針では決めた順序のままに左上から順番に置いていっているので、逆に言えば最初の順序さえいじれば結果的に行を跨いだ変更もできるわけです。ランダムに順序をswapしていく山登り法を書くと、9099622点が取れます。ついに桁が1つ減りました。

seed=25, score=167174

ただ、この時の山登りのイテレーションの回数はなんと65回です。なぜならば、1回1回ちゃんと行の中でも並び替えていて、bitDPの計算が重いからです。
ここで一旦諦めて風呂に入ります。

風呂でぼーっとしていると、「行の中での並び替えって最初に順序を山登りで決めるのであればいらないのでは?」ということに気づきます。そこで、bitDPをやめることにすると、イテレーションを4000回回せるようになりました。これを出すと9186869点で、全然良くなってはいませんが、こちらの方針の方がここから色々改善できることが分かっていたのでむしろ気分は良かったです。
なお、行仕切りの位置も山登り中に動かせるようにしたかったため、遷移は「全体順序の2点swap」「行仕切りを1つ設置/撤去」の2通りで、ほとんどが前者になるように調整しました。

何も考えずに山登りを焼きなまし法に変換し、vectorを配列にするなどの高速化を行ってイテレーションを稼ぐと、6547551点が取れます。

また行仕切りの分け方について考えてみます。試しに 130, 130, 130, 130, 130, 130, 100, 70, 30, 15, 5 にしてみると、手元のスコアが結構伸びました。要求面積的に、縦の長さとして100~200だと収まりがいいケースが多いみたいです。一旦、試した中で一番良さそうだった 120, 115, 110, 105, 100, 95, 90, 85, 80, 60, 40 にしました。
さらに、焼きなましのイテレーション回数をN*Dに比例させるようにし、回せるケースはどんどん回していくことにすると、4381880点が取れます。また午前3時とかになっていたので、寝ました。

seed=25, score=155298

3/29(8日目)

明らかなミスとして、「1日目は仕切りペナルティがない」ということを見落としていたので、実装すると4294689点が取れます。ほんの少しの改善。

ここまで右端の壁を使ってきませんでしたが、一番右の長方形は壁まで伸ばすと節約できるので伸ばすことにします。4089468点。

色々パラメータ変えるも伸びず、諦めモードに。

3/30(9日目)

焼きなましの初期解を変えつつ複数回やるように変更してみたら、手元でちょっと伸びたような気がしつつ提出すると伸びず。その分一回の焼きなましのイテレーションが減るので、単に複数回やるだけだとダメなんですね。また諦めモードに。

3/31 - 4/1(10日目)

最後の夜。半ば諦めながらマラソンの記事を読んでいました。すると、焼きなましの高速化手法として、「一定以上スコア減少することが分かった時点で評価関数を打ち切る」というものを発見(shindanninさん、ありがとうございます)。
shindannin.hatenadiary.com
自分の評価関数は、前半で面積ペナルティ、後半で仕切りペナルティを計算しており、後半の計算でsetを使ったりしているので、前半で打ち切れるとうれしい気がしました。実装すると1.5倍ほど高速化され、イテレーションを増やせました。ただ、提出したスコアは4023567点と微妙な伸びに。

さらにマラソン記事を掘っていると、貪欲とビームサーチの対応についての説明をいくつか発見しました。ビームサーチは実装したことがありませんでしたが、現状の解法が日ごとに最適に割り当てる貪欲法であり、折角のオフライン性を無駄にしていると思っていたので、書いてみようという気になりました。グローバル変数を多用した実装のせいで改造は難航しましたが、何とか夜のうちにビーム幅5のビームサーチが完成。イテレーションは減るものの、目論見通り全体のスコアは伸び、2824303点が取れました。パラメータを多少いじったもののそれより良いスコアは出ないようだったので、これを最終提出としました。

(再掲)seed=25, score=50596

大分頑張ったつもりでしたが暫定38位、最終は35位で、1ページ目の壁は厚いなという気持ちになりました。上位陣には行と列を両方焼きなましている人がいて、やはり行仕切りの位置を最初に決定してしまっているのが良くなかったのかなと思いつつ、列仕切りの組み方コンテストならだいぶ上位なのでは、などと考えていました。
AHC Standings を見ると、自分のプログラムは日数の多い場合に強く、Nの大きい場合に弱いことがわかりました。ビームサーチ化の効果という感じがします。N,Dの値によってビーム幅を変えるなどの改善はできたなと思いました。
また、AHC031 - Event Hall - Statistics を見ると、配置が疎な場合に限っては相対的にとても弱いことが分かりました。こういうケースごとに相対評価するスコア計算の場合、ケースの特性ごとに効いてくるやり方が変わってきそうなので、苦手なケースの特徴を洗い出すのが大事そうです。

感想

スコアをじわじわと伸ばしていくのが気持ちよかったです。ビジュアライザも見やすくてグッドでした。
Algorithm は限界を感じてやめていたのですが、Heuristic はまだ向いている可能性が残されているので、AHCはまた出ようと思います。

集中したいときのためのお菓子と音楽

EEIC Advent Calendar 2023 11日目の記事です。

こんにちは。EEICに所属している taisa1 です。色々な実験を頑張った1年も終わりに近づいてきました。

情報系の実験では一人で作業する時間が長いものもあり、集中力の維持が大切になってきます。自分は基本的に集中スイッチが入らないのですが、たまにスイッチが入ったときにそれを維持するのに役立つのがお菓子と音楽です。今後のためにも、お気に入りをまとめておこうと思います。

お菓子編

実験中によく買いに行っていたお菓子を紹介します。

ガブリチュウ

www.meigum.co.jp

安い。おいしい。すぐ食べられる。作業中に横にお菓子があるとつい気がとられがちだけど、ガブリチュウなら休憩中に食べ終えられる。

ラムネ

www.morinaga.co.jp

高校時代はよく食べていて絶賛していたけれど、最近はあまり作業中に食べるのに向いていないんじゃないかと思っている。
とてもおいしい。おいしいだけに食べ過ぎる。血糖値が上がって眠くなる。食べ過ぎ防止のために袋じゃなくてプラ容器の方を買うようにしたい。

アイス

www.morinaga.co.jp

集中が切れてしまったときに。夏場はよく買いに行っていたけれど冬に食べるのもいいかも。
チョコモナカジャンボとスイカバーをよく食べていた。

タフグミ

www.kabaya.co.jp

見るからに健康に悪そう。でもどんどん食べてしまう味。それだけに少し罪悪感があり、食べたんだし頑張らなきゃなと思う。

かめかめサワーズ

www.nobel.co.jp

ボリュームがないけど逆にちょうどいい。一粒でハードとぷにぷにが両方味わえる。

ガツン!とみかんグミ

greenbeans.com

元ネタは同名のアイス。グミなのに食感が果肉すぎてびっくりした。

バッカス

www.lotte.co.jp

洋酒入りチョコ。口がピリッとして気持ちいい。眠くなった時の弱めの気付け薬。高くてあまり買えない。

音楽編

アルバム単位で聴くことが多いです。曲の空気感が共通している(ことが多い)のと、アルバムを聴き終わるまでは頑張ろうと思えるからです。

I Am Robot And Proud - Lucky Static

open.spotify.com

ずっと耳が心地いいインストアルバム。電子音とコロコロした音。本を読むときにもいい。

TVアニメ「ゆるキャン△」オリジナル・サウンドトラック

open.spotify.com

意識だけでも本栖湖に行きたくなってしまったときに。

haruka nakamura - スティルライフ

open.spotify.com

ミュートピアノという、コトコトした打鍵音が聞こえる優しいピアノの音で作られたアルバム。心が洗われる。

Daft Punk - Random Access Memories

open.spotify.com

名前からしてプログラミングに合いそうなアルバム。名曲がいくつも入っている。

m-flo - EXPO EXPO

open.spotify.com

平成の懐かしい感じ。ノリノリだけど落ち着きも感じられる。この手のサウンドが最近また流行っているみたいでうれしい。

パソコン音楽クラブ - See-Voice

open.spotify.com

潜りたいときに。

いかがでしたか?

お菓子のWebページって、最も遠いはずの駄菓子屋の空間と現代インターネットが繋がってしまったような気がしてドキっとします。
これを読んで気になったものがあればぜひ食べたり聴いたりしてみてくださいね。

1年の振り返り

高校生活が終了し、大学生になる予定です。
高3生活の総括も兼ねて文章を書いてみます。真面目な受験生だったので、話題は受験中心。

〜7月

 引きこもり期間。 
IOIに落ちたけれど、やはり競技プログラミングはやめられず、毎日CFのvirtualをする。CF赤を目指していたと思う。一回惜しいところまで行ったが、なぜかunratedになったせいで帳消しに。
受験については、4月時点で「molって何?」「解の配置問題わからん」というレベルだったので、流石に危機感はあり、1日6時間くらい勉強する。それでも、この状況だと他の受験生は1日10時間勉強してそうだと思って不安になる。

8月

 富岳チャレンジに出たりする。勉強に飽き始める。
このあたりでなんとなく競プロからフェードアウト。
その代わりに、文化祭のことが決まり始めたので、8月最終週から開発の方を始める。
模試を受けたら冊子掲載されてしまい、これまでの勉強が意外とうまくいっていたと分かって安心する。

9月、10月

 文化祭一色。とはいえ空きコマなどに不安な分野はやっていた。特に化学の知識系。
英語をこの時期全く触れていなかったのが痛い。

11月

 模試を受けたら8月より点が低くて萎える。それでも判定は下がらなかったので、危機感は生まれず、あまり気持ちを切り替えられない。
周りは文化祭後に切り替えているようだったが、自分はPCKの本選が終わるまで全く勉強に本腰を入れる気になれなかった。
そして文化祭で作ったゲームを遊ぶためにiPadを買ってしまい、デレステにハマる。

12月

 流石に共通テストの勉強を始める。地理の参考書をぼーっと2周ほど眺め、過去問を解くと7割も取れなかったりして焦る。
6月あたりより熱心に勉強していないことを自覚し、文化祭期間のブランクを考えて不安になる。
塾の最終授業が終わってからは全く外出しない生活が始まる。

1月

 外出は初詣と共通テストのみ。
共通テストまでは毎日過去問をやっていたので、勉強時間だけは毎日6時間くらいあった。
しかし共通テストがうまくいったので、完全に気が緩む。
1月の最後にデレマスのアニメが25話一挙放送されると聞き、全部見て完全に好きになってしまった。

2月

 毎日何かしらの演習はやろうと決め、理科と数学を中心にやる。
数学は安定していた。理科については、得意だと思っていた物理でひどい点数を連発していた。
化学は特に計算問題が元から苦手だと思っていたが、なぜか物理より点数は取れることが多かった。
英語は、中1の時からずっと放置してきた文法は諦め、リスニングと洋書を読むのだけ毎日続けた。肌感覚でやる方が性に合っているらしい。
今考えると、CFの問題文をお気持ちを掴むために速読してたのが割と役に立っていそう。
国語は当日に単語を見たくらい。
勉強時間は、共通テスト前よりは確実に少なかった。150分演習を1回するだけで体力が限界だった。1年前は1日中春合宿の問題を解いていたのに...。

3月

 受験終了したのでゆっくり過ごす。
合格発表後から気持ちを切り替えないとまずそうだが、全然できていない。発表前にスタートしたコンテストがまだ終わっていないのもその一因。

まとめ

ほとんど引きこもってました。
デレステで鷹富士茄子さんをセンターにしていたおかげで受かった説を推していきます。

PCK2020 参加記

高校生として出た最後のプログラミングコンテスト。チーム戦楽しかった。

チーム

with sinatori
戦略としては、最初の方は交互に解き、後半はそれぞれが得意なものをやる感じ。地力の差があまりなさそうだったので。
データ構造、グラフは自分で解きにいき、文字列、幾何は相方に投げていた。本選前のバチャの感触としては、8位以内はまあいける、3位以内も狙えそうだと感じた。

予選

あんまり覚えてないので適当に。
11完2ペナで1位。何気にプログラミングコンテストで1位取ったの初めてかもしれない。
2,4,6,8,10,12を担当。この時は完全に偶奇で担当が分かれた。

1~4はスムーズに通ったが、6が正四面体を転がすとかいう謎問題で、詰まった。そのため1~6が通ったのが開始80分後とかで、この時は普通に通過圏外だった。
6はずっとスマートに解こうとしていたが、諦めて紙を破って正四面体を実際に作り、それを見つつ大量の場合分けをして通した。

そこからすぐ7,8をほぼ同時に通し、9,10に取り組む。ここまで2人の提出タイミングがずっと同じだったのが面白かった(別々の問題のはずなのに)。
10は平方分割を考えるとすぐに解け、奇跡的にノーペナでAC。ここまでで2時間半くらい。

9がかなりつらそうだったので応援しつつ、どこかのチームが通していた12を眺める。
雰囲気で重心分解を考えるとうまくいくことが分かるので実装すると、ほぼ詰まることなくAC。
しかもこの間に9のバグが取れていたらしく、9もACして11完に。9と12を同時に提出したので、ほぼ同時にACが帰ってきてテンションが上がり、思わずハイタッチしてしまった。

順位表を見た感じかなり高順位で通過できそうだったのでとても嬉しくなった。11は少し考えたが解けなかった。

10、12がこの速度で解けたのは完全に運だが、ともあれ成功したのでヨシ。

本選

こっちはかなり強烈な記憶になった気がする。
10完∞ペナで7位。悔しい。
2,4,7,8,11,12を担当。

1~3はスムーズに通る。
4の解法が10分考えてやっと分かったので、かなり焦りながら実装した。するとWA。
初期化忘れかと思って直してもWAのままで、序盤で2ペナを作ってしまったことで完全に冷静さを失った。そこで、落ち着くために7に行くことにする(6が幾何だった)。
すると7も全然綺麗な解法が分からず、分かった解法も自力で多倍長整数を実装する必要があり、ライブラリも持っていなかったので焦りまくった。
それでもなんとか適当に実装するとAC。この時点で75分位経っていた。

4が嘘解法だと思ったので、7をやっている最中に相方に4を考えてもらったが、普通に合っているらしい。コードを確認すると最後の処理を書き忘れていただけだった。申し訳なさすぎる。
8の見た目がまともそうだったので考えると、2番目の最短路を求めるだけの問題だったので安心した。普通にAC。この時点で2時間くらい。

その間に相方に投げていた6がバグが取れないらしかったので、9に行ってもらう。
ここで自分が6を見たほうが(精神衛生上)良かった気もするが、焦っていたのでそんなことは考えず、12に特攻する。たぶん2人とも焦っていて良くなかった。

12はJOIのバブルソートを解いていれば貼るだけの問題で、当然貼るのは不正なので頑張ってライブラリを写経する。SuperConで鍛えた写経能力の見せ所(適当)。
2Dセグ木は中々重かったが、1ペナでAC。この時点で2時間半くらい経っていたが、流石にこのあたりになると頭が冷静になってくる。ペナ数ももはや気にしても仕方のない領域に来ていた。
そこで6を眺めると、すぐにバグの原因が分かる。ACして一安心した。

相方が、9が分からなくて10が分かったらしいので、自分が9をやることにする。しかし分からない。AtCoderみたいな問題を出さないでくれ。

残り1時間くらいのタイミングで初めて順位表を見ると、7位くらいだったが、今から10,11を通せば良い位置に行けそうだと踏み、11をやることに。
秘技・制約エスパーを用いるとO(3^n)のDPが使えそうだと分かる。パスごとに最小コストを前計算し、強連結成分にパスを追加しながらDPしていく感じ。
残り30分くらいで実装は終わったが、微妙にバグっていたので気合を入れてデバッグをすると残り10分でバグが分かり、残り7分くらいでAC。流石に脳内物質がドバドバ出た。

隣を見ると相方が10のWAで苦しんでおり、自分は問題すら見ていないのでお祈りをしていたが、流石にヤバそうだったので見る。バグらしきものは修正できたものの、WAのままコンテスト終了。方針ミスだったらしい。幾何本当にむずそう。

感想

チーム戦、戦略とコミュニケーションが大事になってきて中々面白い。ただミスった時はだいぶメンタルが削られる。ICPCに出るかどうかは迷い中。
あと幾何2問は、きついです......。それでも全完するチームがいるの、何??

JOI2020春合宿 参加記

結果から言うと、5位で代表にはなれませんでした。
以下、日記です。(全てその日のうちに書いたもの)

Day0

朝起きると体調がよろしくない。本選の時もそうだったので情報オリンピックに呪われているんじゃないかと思う。
Library Checkerを埋めて「やがて君になる」を読んでいると夜になっている。
早めに寝付けたものの、1時ごろに目が覚めてしまう。ここ数年でこんなことは一度もなかったので不安になった。

Day1

体調はあんまり変わっていないが、とりあえず5時間はやりきれそうなので出発。
控室が同校率80%とかで面白かった。競技会場に入るとさすがに緊張してきたが、今までの5時間バチャの戦略を思い返して心を落ち着けた。

競技

最初に全部ざっと問題文を読んだ。構築-構築-クエリゲーで、嫌な気持ちになった。
1問目の見た目が明らかに簡単枠なので、絶対に解くという気持ちで考える。
一瞬で判定の方法(未証明)が思いついたが、証明していないので当然構築できない。
緊張もあって1時間ほど進展がなかったので、とりあえず判定が正しいか確認した。"-1"を提出した場合と同じケースがACしていたので、正しいと分かる。(最悪)
これが正しいなら絶対にみんな通すはずだと思い、一度トイレに行って頭を切り替えると、普通に構築できることが分かる。
1時間半で満点。2回目の春合宿にして初めて満点が取れて嬉しくなった。

2問目が不可能枠、3問目が難易度11くらいに見えたので、3問目を考えると、小課題3まで分かる。
どうせデータ構造に強い人間が小課題4を解くだろうと思い、小課題4を1時間ほど考えるが、分からない。
後は部分点回収をしただけだが、3問目の小課題3がWAになり、1時間以上原因が分からなくて焦った。
原因は、二分探索のokの初期値を便宜上0にしていたが、実際は0でOKかどうかは確認しないと分からないというミスだった。こういう時は-1にして、判定関数内で場合分けするようにしたい。
2問目は、K=1の時は遅延セグ木で解けるのでそれで2点を得たが、実際はもっと楽にできるらしく煽られた。

結果・反省

結果は100+2+12=124点で4位タイ。やるべきことをやれたかなという感じ。難易度は10-12-12だと思った。
2:30くらいから部分点回収しかしていないのは、単に「とりあえず取っておこう」で書き始めたものが無限にバグったせいで、後半の時間はほぼ考察ができていない。
おそらくインタラクティブが出た時にも同じようになるので、最初の2時間の考察の集中力を上げていきたい。

Day2

よく休んだら体調がかなり良くなって安心した。代わりに花粉がつらくなり、ティッシュを買いに行ったらなかったので、ラムネを買った。(英断)
ラムネを体に詰め込んでから競技開始。

競技

全部読むと、インタラクティブ-グラフ-数え上げ というセットだった。どれもそれなりに点数が見込めるので、時間配分に迷った。
直感で、2->3->1の順に見ていくことにした。(敗因)
2問目の見た目が過去問と似ており、途中まで似たような考察ができたので、簡単枠だと思って考察をする。
10分ほどで解法(嘘)が思いついたので実装する。実装をサボってまず1点を取りにいった。しかしWA。
実装ミスだと思い、色々書き直したりしていると1時間半ほど経っている。ここで落ち着いて図を書くと、1回の追加で2つ以上マージされる場合があることに気づく。この場合でも17点が取れる正解法がすぐにわかったが、面倒さに比べて点数が少ないと思ったのでやめる。
3問目を見ると、意味不明な数え上げをDPでやれと書いてある。図を書いて観察すると、状態数3^N*NのDPが分かるが、6点しか取れない上に実装が重い。そして多項式時間に落とせそうもない。
この時点で3時間ほど経っていて、まだ1点も取れていないことからかなり焦っていた。そのため、2問目がやっぱり簡単枠なんじゃないかと不安になって何度も2問目と3問目を行き来していた。
結局、2問目も3問目も不可能枠だと信じ込むことにして、それなりの点数を得やすいインタラクティブに行くことにした。

小課題1すら分からないが、先に小課題2,3を考えてみることにすると、嘘考察を交えながらも何とか思いつく。一瞬で実装でき、40点。ここまで3時間半。少し精神に余裕ができる。
小課題4を考えてみる。制約が二分探索をしろと言っているので考えると少し変えるだけだと分かるが、バグらせる。それでも30分ほどで通る。冷静な心を取り戻す。
2問目の17点を実装するか、1問目の満点を考えるかで迷ったが、他の人間が3桁点取っていると思っていたので、代表争いを考えて1問目の満点に行く。
しかし、疲れからか、嘘解法に突っ走ってしまう。結局解けなかったが、その嘘が小課題1に偶然適用できたので、使うと4点が取れる。そしてコンテスト終了。

結果・反省

結果は64+0+0=64点で、Day2/累積ともに3位。順位的には申し分ないが、6位と24点、13位と88点差と、全く安心できない。他の人の失敗に救われた感じはある。
最後の1時間で、2問目の17点を取りに行くべきだった。そこは戦略ミス。
また、最初に2問目の嘘解法の実装に時間を溶かしたのがとても痛い。競技の最初で失敗すると後に響くので、実装は慎重にしていきたい。
1問目を解いているときに、何度も諦めて2問目をやろうかと思ったが、解法に近づいているという感覚を信じられたおかげで耐えられた。大事。
難易度は11-11-12だと思った。もう少し差がつくセットを出してほしい気もするが、抜かされるのは嫌という...。

Day3

行く途中は緊張していたが、競技会場に入ると3日目なだけあって落ち着いた。

競技

最適化-グラフ+データ構造-コミュニケーション というセット。
1問目がとっつきやすそうなので見ると、ちょくちょく良い性質が見つかるが、スルーしてしまう。小課題がDPだと主張しているが、順序付けを色々考えてもうまくいかない。不安だったが難問枠だと信じ、1時間ほどで飛ばす。
2問目は、勘違いをしていて15分ほどで嘘解法を思いつく。幸いすぐ気づいたが、正しい考察をすると、実装量が異常であることが分かる。本質部分が2つに分けられるが、簡単な方のパートの時点で既にヤバいので、最後にやることにする。

3問目を読み始めたのは開始2時間後くらい。すぐに15点解法(嘘)が思いついたが、提出しても4点しか取れない。ただ、15点と85点で完全に問題が分かれているので、一旦飛ばした。
小課題5はすぐにわかったが、面倒なので実装しなかった。
小課題6は、本質的な考察は30分ほどでできたが、実装を始めると、枝分かれで詰むことが分かる。考察ミスだと思って30分ほど考えても分からなかったので、問題文をもう一度読み直すと、見落としていた情報に気づく。
これを利用すると満点が取れることが分かったが、この時点で4時間経っていた上に場合分けが多く、ミスを埋めこんでいた時点で詰むと思った。だが、1,2問目はこの状態からまともな点数を取れる気がしなかったので、これに賭けることにした。
実装しながら場合分けを考えていたので、書き上がったのは残り30分のとき。しかし提出すると0点である。WAケースを探そうと手作りのケースを色々試すと、運良く見つかる。それが合うように直して祈ると、85点が帰ってきて安堵した。
この時点で残り18分で、絶対100点を取るという気持ちで残り11点の考察をする。すると5分ででき、手を震えさせながら提出すると、合計100点になる。安心したので後はお祈りをしていた。

結果・反省

結果は0+0+100=100点で、Day3/累積ともに5位。2人に抜かされた形だが、4位との点差は絶望的なわけではない。
1問目で小課題1すら取れなかったのは完全に実力不足。2問目は3人に解かれているが、正直実装コストを考えると手を出さなくて正解だった。
3問目は、問題文の見落としが痛かった。明日は絶対にないようにしたい。また、場合分けは実装前に詰めておくべきだった。立ち回り自体は悪くなかったと思う。
難易度は12-11-11だと思った。
明日の今頃は代表が決まり、自分のJOI人生も終わると思うと感慨深い。Day4は悔いのないようにしたい。代表目指し頑張りましょう。

Day4

二度寝をしてしまったせいで、競技開始前にあくびがたくさん出た。こういう時にやったバチャは大体失敗しているので、不安になりながら競技開始。

競技

木-Output only-最適化 というセット。
1問目を見ると木の問題で、得意だと思っていたので嬉しくなる。小課題1,2を考えるといくつか嘘を思いつくので実装すると、0点。少し考えてもうまくいきそうにないので小課題3を考える。
すると、また嘘を思いついてしまう。ちゃんと考察すれば満点に繋がる解法だったが、適当すぎたせいで実装しながら頭を壊してしまった。
1時間半くらいで、一度諦めて3問目へ。小課題2までは一瞬で分かるが、小課題3以降は人間に不可能そうな雰囲気が漂っている。
この感じだと1,3問目が不可能枠で、Output onlyで点数を稼がないと代表になれないと思ったので、2問目でまともな点数を取りにいく。

少し考えると、焼きなましが効きそうだと思ったので、山登りのようなものを一旦書く。しかし、多く回すとWAになってしまう。この原因究明に1時間ほど掛けてしまった。提出すると37点。少し改善すると39点になった。
この時点でもう冷静に戦略を考える頭は残っていないので、そのまま2問目を伸ばしに行くが、全く伸びない。
そこで、とりあえず自明な3問目の小課題1を実装しながらプログラムを回すことにしたが、3問目は0点で、2問目は回数を増やしても全くスコアが伸びない。3問目のWAは意味不明だったので2問目と心中することにする。
実は「山登りのようなもの」は山登りになっていないことに残り30分ほどで気づく。しかし、もう自分の頭には肥大化したプログラムをどう変えれば山登りになるのか分からず、気がついたらコンテストが終わっていた。

結果・反省

0+42+0=42点、Day4で9位、累積5位。代表ボーダーとの点差は100点以上開いた。
もうガチでOIをやることはないので特に書くことはないが、受験生となる身としては、大事な日には二度寝をしないようにしたい。

供養

JOI過去問埋め
f:id:TA1SA:20200323172839p:plain
海外OIなど(oj.uz)
f:id:TA1SA:20200323172359p:plain

ポエム

くやしい
受験勉強頑張ります
日本代表を応援しています
PCKはいい成績とれるといいな(誰と組むんだろう)

JOI'17春合宿 Dragon 2

幾何パートとデータ構造パートに分かれている問題。幾何パートで下手な考察をして、場合分けと実装を爆発させてしまった。
データ構造パートで解説と違うことをしていたので、主にその部分について整理する。

問題

atcoder.jp

解法

解説にある最初の満点解法の、「aとbを入れ替える」条件を、サイズに依存するようにした解法。
(D1,E1)を点P1、(D2,E2)を点P2とする。また、i番目のドラゴンの位置をp[i]とする。
図を書いて頑張って考えてみる。
すると、各クエリでは、iを種族fの点、jを種族gの点として、(p[i]とP1のなす角,p[i]とP2のなす角)と(p[j]とP1のなす角、p[j]とP2のなす角)がある順序関係を満たすような(i,j)の組の数を数えればよいということが分かる。
これは、それぞれを二次元平面上の点と見て、x座標でソートしてy座標をBITで見れば、O((fのサイズ+gのサイズ)logN)で可能。全てのクエリでこれをすると、計算量はO(QNlogN)で、小課題2が解ける。
ここで、「同じペアはクエリに現れない」「各種族のサイズの合計がNで抑えられる」ということに注目すると、オーダーをmin(fのサイズ,gのサイズ)のみに依存するようにすると計算量が落ちそうという予想が立つ。
毎クエリでf,gの小さい方だけ走査するとき、走査数の合計はどのくらいだろうか。各種族のサイズで場合分けしてみる。
・サイズが√N以上のもの:それを走査するのが必要な相手はO(√N)個で、サイズの合計がO(N)なので、O(N√N)
・サイズが√N未満のもの:毎クエリO(√N)なので合計でO(Q√N)
よって、O((N+Q)√N)回となる。
fとgが非対称に見えるが、図を書いて頑張って考えると、逆にしても実際の処理は同じような問題に帰着できることが分かる。
サイズの大きい側のvectorにpush_backしていき、クエリを読んだ後に全てオフラインで処理すれば、1クエリO(logN)で分かるので、全体でO((N+Q)√NlogN)で解けた。

実装

ラムダ式でまとめたけど場合分けがひどい......。
atcoder.jp

感想

「本番中に通す難易度」は絶対に難易度12だと思う。2017年のめちゃくちゃ強い人たちが誰も小課題2以降を取ってない時点でお察し。

JOI'17春合宿 Port Facility

天才数え上げな見た目をしているのにゴリゴリデータ構造ゲーに落ちるという面白い問題。
かなり苦戦して通したので整理しようと思う。

問題

atcoder.jp

小課題1

条件を満たすように、区間の集合を2つのグループに分けた時の通り数を求める問題。
問題文にやることが書いてあるので、O(2^N)で全探索した後にO(N)でシミュレーションすれば良い。これでO(2^N*N)で解けた。

小課題2

どういう時に条件を満たすか考えると、「同じグループ内に、部分的に交差している2つの区間が存在しない」時だと分かる。
このような区間同士に辺を張ったグラフを考える。グループ分けを頂点への色塗りとして捉えると、辺の2端点の色が全て異なることが条件。
つまり、求めるものは二部彩色の通り数である。
二部グラフでないならば答えは0。二部グラフならば、連結成分ごとに2通りの彩色方法があるので、2^(連結成分数)が答えとなる。
二部グラフ判定と連結成分数を求めるのはDFSで容易にでき、グラフの辺数はO(N^2)本なので、O(N^2)で解けた。

小課題3・4

定石通り、区間を左端でソートし、順に見ていく。
この時、iと辺が張られるのは、「今までに出てきた区間のうち、Ai≦右端≦Biのもの」である。
区間にまとめて辺を張りたいので、Segment Treeで右端を管理してみよう。
各ノードには、「ノードが表す区間に含まれる、今までに出てきた右端の集合」を乗せる。
まず、連結成分数だけを求めることを考える。
「[Ai,Bi]に対して辺を張る」というクエリが飛んできたら、それが被覆するノード内の集合とiが同じ連結成分になるので、これら全てと辺を張れば良い。
こうすることで、愚直に張ったグラフと連結性が等しいグラフが出来上がる。
これだけだと計算量的には何の改善にもなっていないが、辺を張った後に集合をクリアするようにすれば、全体でO(NlogN)でできるようになる。今は連結性だけに興味があるので、ノード内に同連結成分のものは2つ以上必要ないからである。
右端をSegment Treeに追加する際には、その座標を含むノード全ての集合に追加するだけで良い。
構築したグラフ上でDFSすれば、連結成分が求まる。ここまで全てO(NlogN)で可能。
だが、このグラフでは二部グラフ判定はできない。ここからは二部グラフ判定の方法を考える。
連結成分を求めるのに使ったSegment Treeを活用したい。
ここで、各ノード内の集合について、「右端を追加する時に集合に入ったもの①」と、「辺を張る時に便宜上加えたもの②」に頂点を分けることを考える。②は高々1つしか存在しないことに注意。
この時、①と②の色は必ず異なり、①同士、②同士の色は必ず等しくなることが分かる。辺を張る時、両端がどっちに属しているかは明らかなので、各辺ごとに両端の色の関係が分かる。
この情報によって、DFSをする際に、色を決めてしまうことができる。ただし、ここで決めた色は必要条件にすぎないことに注意。
後は十分性をチェックすればよく、色が決まっていることから、実際にシミュレーションすれば良い。
全体でO(NlogN)で解けた。

実装

Segment Treeのノードにはvectorを乗せている。
定数倍で落ちたので、グラフの辺数を抑えるために、UnionFindを使って必要な辺だけ張るようにした。
atcoder.jp

感想

考察中にいい性質(使えるとは言っていない)が色々見つかって迷走してしまった。
こういう問題を短時間で解ける日は来るのかな......