はじめに
今年のJAG夏合宿は,国内予選と同じチームK1NTOKIのメンバーで参加しました。
いい感じに得意分野(と苦手分野)が分かれていてとても良いと思っています。
チームの方針としては,Aをキクタ ンさん,Bを僕が見て,それ以外をk1suxuさんが見るという感じです。ABを通した段階で解かれているor解けそうな問題があれば,適切に振り分けて実装するという流れです。
day1
コンテスト
B
コンテスト開始直後,予定通りまずBを見ました。直感的にソートが最善だとは思いましたが,証明がなかなか思いつかず。k1suxuさんに相談したらよさそうみたいなことを言われたので,信じて書きました。通ったのでよかったです。
L
Lが早々に解かれていたので,Lから逆順に見ることにしました。k1suxuさん枠を感じたので考察しつつ投げると,商列挙ですとか言われたので,一旦それを受け止めて考察を続けます。
A
その裏で,A問題にキクタ ンさんが苦戦しているのを観測します。提出してWAが返ってきたものの,その理由が良く分かっていないらしいです。k1suxuさんも加わって原因を特定していました。2人がかりでも解き切れていないようだったので,首を突っ込んでみると「問題概要は聞かないで一人で問題を読んでみてください」と言われました。誤読の可能性が高いため確かに有効な方策だと納得したので,落ち着いて読みます。読み終わって解法を伝えた結果,キクタ ンさんは(3, 3)と(3, 3)みたいなまったく同じペアを数え忘れており,k1suxuさんはidenticalを「異なる」という意味でとっていたようです。僕もidenticalの意味にはあまり自信がなかったので持ち込んだ英和辞典を参照して,「等しい」という意味であることを確認しました。意味を見てから,ああ確かにidentity mapで恒等写像 とかidentityで自己同一性とか言いますねと腑に落ちました。意味が明確になったのでそれを整理してAC。
F
次に,Fがほとんどやるだけだとk1suxuさんに言われたので,問題と解法を聞いて確かによさそうと言いました。しかし,問題が簡単すぎたので何かおかしいと思って問題文を一人で読んでみます。すると, の は頂点番号ではなく,実は今までの操作回数であったことが発覚しました。これを伝えて考察しなおすと,結局LCA に行って偶奇のいい感じのところで待機を繰り返すとよいという結論になったので,実装を任せました。途中,Sを最初に2倍にすると偶奇性のところで何も考えなくてよいと言う係をして,AC。
L
さて,Lに戻って解法を考えます。k1suxuさん曰く,商列挙を2回やっても,調和級数 的に考えて全体 とかで抑えられているのではないかとのことでした。直感的にそんな気がしたので,実装をお願いします。程なくして書き終わって提出してみたところ,TLEだったとのこと。最大ケースを手元で実行してみるとかなり時間がかかっているようです。ここで,k1suxuさんがメモ化すれば行けそうと主張したので,どこをどうメモ化するのか良く分かりませんでしたが任せてみます。すると,最大ケースが3secほどで終了しているのを観測しました。TL4secなので通るだろうと思って投げますが,またもTLE。そこで,テストケースはrand(1e8, 1e9)を100回みたいな方が実行時間がかかるんじゃないかと提案してやってもらうと,これが4secくらい(あまり正確な秒数は覚えていません)かかっていました。その後k1suxuさんがいろいろ工夫して提出してTLEを重ねながら,何回か提出してACを得ていました。何で通ったかは良く分からないとのことでした。
G
Gがある程度解かれていたので,キクタ ンさんと2人で考えていました。直感的には左下 マスは好きな色に塗れそうということは分かりました。ここで,前回のARC205B を思い出して,実は操作に対して不変量があるのではないかと考察をすると,斜めに見たときの白の個数の偶奇が操作回数の偶奇により不変であることに気付きます。これに気付きさえすれば,あとは01列に対する区間 XORと全体の0の個数が取得できればよいので,遅延セグ木で解けそうです。具体的なデータをどう載せるかについては良く分からなかったので,k1suxuさんに頭を下げて,写経とともにやってもらいました。最初の白の個数を数えるのが少し大変でしたが,無事AC。
E
E問題は,良い感じにグラフを構築できればあとはやるだけであるということまではk1suxuさんが考察していました。ただグラフの構築が大変で,途中で球面上にある円の包含関係をどうすればいいですかと尋ねられたので,ベクトルの内積 とかをこんな感じで使うとたぶんいけますと図と式を描きながら返しました。その後もk1suxuさんは実装を続けていましたが,WAで最終的に通し切ることはできませんでした。
D
最後に2人はD問題に取り組んで,それらしき解法が生えたそうです。実装して愚直と比較するランダムテストを回すも, というパターンでのみ(必要十分かは知りません)WAで落ちてしまうことを発見しました。しかし残り時間も僅かだったので,こんな変なケース入ってないだろ!wと提出すると,なんとACが返ってきたそうです。
6完11位でした。
day1結果
コンテスト後
この日の夜に関してはちょっと用事があったので,解説が始まったくらいのタイミングで帰ってきました。E問題以降がどうなったかを知ったのはその後です。
悪くない成績でしたが,欲を言えばEを通しきってあと1完欲しかったです。
Lについては,どうやら計算量解析が間違っていたらしく,eightくんが であることを示してくれたそうです。額面 ですから,TL4secとはいえ相当厳しいですね。計算量解析についてはおそらくですが,
とかでよいかと思います。調和級数 がlogになるのは, の積分 が出てくるのがポイントであったことを考えれば,確かにという感じです。少し自分が早合点しすぎてしまったと思います。
お楽しみコンテスト
今年はコンテストがない時間に取り組むコンテンツとして,お楽しみコンテストが用意されていました。問題は全体的にCTFっぽい感じでした。
僕はwelcome以外はロリハのハッシュ衝突2問が解けました。解けたと言っても,Rolling Hashを殺す話 | PDF にすべてが書いてあったので,これを実装しただけです。こないだ苦労しながらSageMathを入れた甲斐がありました。
Good Numberはジャッジステータスや実行時間でテストケース特定を考えましたが,ジャッジがあまり安定していなかったのと最大実行時間しか表示されなかったので諦めてしまいました。
ABC
土曜日につきABCがありました。結果は惨敗。F問題のメビウス 変換というやつは,名前だけなんとなく聞いたことあるけど何やってるかは全く知らないシリーズでした。ただ,種類ごとにまとめ上げたあと適切な二項係数をかけるという方針でも解けるらしく,単純な実力不足を痛感させられました。
翌朝,k1suxuさんにメビウス 変換を教えてもらい,n次元超立方体の00...0に絵の具の塊を置いて,スライドさせながら全部の頂点をちょうど一回ずつ塗っていくイメージを得ました。次に出会ったときには解けるようになっていたいです。
day2
コンテスト
B
B問題はDPやるだけと書いてあるのでそれをします。爆速で実装が終わって9分でAC。FAが7分だったので惜しかったです。
この時点でAB以外のACが出ていなかったので,Lから見ていきました。Lが激ヤバドミノ敷き詰めパズル,Kがデータ構造構築っぽい何か,Jがたのしいパズル,Iが01 on Treeの進化版で,Kが若干可能に見えるものの,すぐ解けるものは見つかりませんでした。
A
A問題を横目で見ると,2人で考えていましたがかなり苦戦している様子でした。問題を聞いて,「外積 0かつ内積 負です」という係をしました。それをキクタ ンさんが実装してAC。
ここ再び順位表に目をやりますが,AB以外の問題をどのチームも通していないという異常事態に。どの問題を解くか決めあぐねていると,FGJKにFAが出てこれらに着手することが決まりました。
F
Fは僕が,Gはk1suxuさんが見ます。FはARC071C を見たことがあったので, が不変量になっていることにはすぐ気づきました。k1suxuさんから,swapが必要になることはほとんどなく,必要になったとしても1010...のパターンで1回のみであろうという大胆予想を告げられました。前述の不変量から最終的にどの文字列になるかは分かりますから,mod 3でいい感じに累積和をもって,後から0xAA...をXORしてRLEしてswap必要分を計上すればよさそうです。難易度的にもちょうどいいだろうというメタ読みをして,信じて実装に取り掛かりました。実装している間にキクタ ンさんがなんとなくの証明をしてくれたので,ある程度の自信をもって書き切りました。提出した直後に という文字列が目に入って,modを取っていないことに気付き大反省。64bit整数で十分なのでうっかりしていました。本番でこういったミスがないように気を付けていきたいです。mod取ったらちゃんとAC。
直後にk1suxuさんが書いていたGが通りました。
J
さて,一段落したのでみんなでJKLを考えます。Jは結局どちらの角で曲げるかの2通りしかないので,色 と色 について, の曲げ方0と の曲げ方1は共存できない,というような条件がたくさん並ぶことになることが分かります。共存できるかどうかの判定は,最初は無限場合分けをしようとしていましたが,k1suxuさんに曲げ方を全探索して線分の交差判定でいけると言われたので,なるほど賢いとなりました。条件の矛盾判定は,UnionFindで何とかなるかと思っていたのですが,実装途中でうまく表現できないことに気付いて立ち止まりました。冷静に考えると,ド・モルガンの法則を上手く使ってやれば, と,2-SATの形になります。あとは写経するだけです。
2-SATライブラリを写経しているときに,val[e] ?: dfs(e)という三項演算子 の真のときの値を省略する記法に出会いました。その場では良く分からなかったのでそのまま写してACしましたが,気になって後で調べました。するとどうやらGCC拡張でこの記法は認められているらしく,ここではval[e] ? val[e] : dfs(e)のことらしいです*1 。0以外の評価値ならそのまま返す,でなければ別の値を返すということができる優れものです。ただし,評価は一度だけしか行われないので注意が必要です(が,これはむしろ嬉しいと思います)。
ここでクイズ
#include <bits/stdc++.h>
using namespace std ;
int main () {
int i = 0 ;
vector <int > a = {1 , 0 , -1 };
cout << (a[i++] ?: a[i]) << endl ;
return 0 ;
}
は何を出力するでしょうか。
閑話休題 。Lを書いていたk1suxuさんと何度か交代しながら,Jを一発で通しました。
K
その後,Kでキクタ ンさんが,元々の列が長さ を超えてしまえば,操作を逆順に後ろから考えたときに と に分かれるので,大きいほうだけ分かればよさそうと言いました。解説のHint1とかHint2を開けた気分で膝を打ちました。それは天才すぎませんかとなって,Lを書いていたk1suxuさんも一旦手を止めてそれはとてもよさそうと言うくらい天才でした。stackをN本持って再帰 をいい感じにとかやっていたのが馬鹿らしくなるくらい天才でした。バグを生みつつ爆速で実装しましたがWA。見返してみると「元々の列が長さ を超えてしまえば」をちゃんと考慮できていませんでした(それでもサンプルがあってしまうのがいやらしい)。長さ を超えるまでには高々20回とかなので愚直で十分です。これを直してAC。今日は自分の不注意で2ペナしているので悲しいです。
L
あとはLを通し切ることができればかなり嬉しいです。2人で考察は終わっており,
車を遠くへ「ワープ」させるのは明らかに無駄(ワープできるならそこに新しい車を詰めれば終わり)
1マスの車のスライドで空白を上手く移動させ,2マス分隣り合わせたい
チェス盤を考えると,別々の色の空白を移動させるべき
これらを踏まえて,マス間に辺をはったグラフを考えて,BFSして経路復元して,swapを繰り返して盤面を構築しているそうです。
すでにこれで1回提出しており,なぜWAなのかが分かっていないとのことでした。僕も実装を眺めますが,特に怪しそうなところは見当たりませんでした。
しばらく進展のない時間が流れていましたが,残り20分の時点で,キクタ ンさんが車の「回転」 に気付きます。
#^.
.v#
は中央の車を45度「回転」 させることにより,
#<>
<>#
とできます。これを踏まえてk1suxuさんが斜め移動も許可した辺を追加して実装を頑張りましたが,回転の盤面構築が複雑で,残念ながら間に合わず。
6完10位でした。
day2結果
コンテスト後
k1suxuさんがLの実装をしていましたが,斜め移動もあることを考慮して最初の方から実装し直したらあっさり通ったそうです。考慮漏れに気付いたタイミングが少し遅すぎたと反省。
余談として,実はday1の自己紹介の時点で気付いていたのですが,高校のときの部活の先輩と偶然にも再会しました。文化祭はどうだったとか,かつての人々は今どうしているだとか,久闊を叙すなどしました。
Writer陣予想コンテスト+懇親会
day3のwriter(たち)を予想するコンテストが唐突に始まりました。スタッフの誰か?いやでもわざわざ出題するということは…?とか読み過ぎてどっちが表か分からなくなってきたので,どうせ誰も当てられないだろうということで,その場合の最適解であるところの空集合 を提出しました。
懇親会では,双子,実力あるチーム,海外の強い人,LLMなど,議論が白熱しました。中盤になって誰が何点を得たかの情報が公開され,自分が空集合 提出バトルに敗北していることを確認し,同時に単独writerであることが確定しました。
懇親会の終盤,過去の問題提供者の情報を精査した参加者によってwriterが特定され,その正体がDispersionさんであることが明かされました。直前までwriterが誰であるか何食わぬ顔で一緒に議論していた人がまさかwriterだとは思わず,とても驚きました。
day3
コンテスト
B
いつものようにBから読み始めます。問題はすぐに理解できましたが,優先度付きキューに入れる 解法しか分からなかったので,k1suxuさんと相談しました。程なくして,使う最大コストでの二分探索でいけると言われ,確かにそれでよさそうだと思いました。ただ,最大値がかぶっているときに面倒なことになりそうで,こういった実装は自分は得意ではないので,k1suxuさんにそのまま投げました。しばらくしてAC。
Aは特にバグらせることなくキクタ ンさんが通してくれました。
H
順位表を見るとHがありえない速度で通されていたので見ます。0を避けるように余りを取っていくと,集合の要素たちが区間 をなすのは分かりましたが,具体的にどうすればよいかはよく分かりませんでした。そこで,day2を思い出して逆順に見ることにします。区間 の長さは変わりませんから,最後の一歩手前は になるのが理想です。ここから1手ずつ戻していくと,最大値+1を各要素に足していったものが理想ですから,これを基準に与えられた区間 を考えればよいです。よって, から始めて2倍して1足すを繰り返し,どこで を超えるかを見ればよいです。以上を実装してAC。
J
Jを考えていた2人に問題を振られたので見てみるとグラフの問題でした。こんなのはオイラー 歩道と同じような感じで,入次数とか出次数とかの偶奇か何かだけで決まるのではないかと雑なことを投げました。そのあとk1suxuさんといろいろやり取りをして,弱連結成分ごとに問題を見たとき,
が答えであろうという結論になりました。連結でなくても孤立点が存在するときにオイラー 回路が存在する場合があることを知っていた*2 ので,k1suxuさんが実装に取り掛かる前に,孤立点だけ例外処理をする必要があることを指摘しました。k1suxuさんが1ペナで通しました。
K
次に爆速で通されていたKを見るとゲーム理論 でした。局面が の2次元で表されることはすぐに分かったので,2次元平面にプロットして 局面と 局面を分けていきます。終了局面が階段状になることにはすぐ気づいたので,これをもとに局面 がどちらなのかを判定したいです。しかし,いつまでたっても規則性が分からず,データ構造などで高速に計算する方法も分かりませんでした。キクタ ンさんもたくさん手で実験していましたが,その規則性をつかんではいませんでした。
F
Kが全く分からないのでFにうつります。かの有名なAntsを題材とした問題で,各蟻の衝突回数 が与えられているので,それが達成可能か,可能であればその初期の向きを出力せよという問題です。最初の方は,k1suxuさんと一緒に蟻の衝突を頭で考えていましたが,ミスも多く全く見通しが立たなかったので,とりあえずダイヤグラム を書いてみることにしました。
ダイヤグラム
黒い半直線の上に重なっている紫や青の線が,個々の蟻の動きを表しています。
すると,蟻 は蟻 か蟻 としか衝突しないことが分かり(考えみると当たり前),個々の蟻の衝突回数を見るよりも,蟻 と蟻 の衝突回数 を見た方がよさそうだなとなりました。 は, から簡単に計算できます。この時点である が負になったり だったら論外です。
このままの図では として数えるべき箇所が斜めになっている箇所もあって数えにくいので,思い切って半直線を直線にしたうえで,線の間隔を等間隔にしてみます。すると,以下のようになります。
線を等間隔にした図
これに伴い,x軸が曲がってしまいましたが,適切に直線の間隔を調整することによりいつでも戻れるので気にしないことにします。
さて,これにより大幅に問題が単純になりました。使うLの個数とRの個数を決めてしまうと,
本のまっすぐな糸と 本のまっすぐな糸からなる,交点を 個持つ網を作り,45度傾けて置く
上から 番目の段には,右から 個の交点すべてに印をつける
印がついた交点とついていない交点を分断するように,上から刀で「まっすぐ」(各糸についてちょうど1回ずつ切るように)カットすることができるか?
という問題になります。カットできたとすると,刀がLとRどちらの糸を切ったかを上から並べれば,それがそのまま答えになっています。
以上のことをよく観察すると,網が折れるまでの「前半」の段(上から 段目から 段目)と,折れてからの「後半」の段(上から 段目から 段目)について,
前半の段では は か しか許されない
後半の段では は か しか許されない
逆に,この条件を満たしていれば,上から刀でまっすぐ切れることが分かります。
ここまでくれば後は簡単で, を見て適切に (および )を決めた上で,
により,右詰めで印をつけていく
右下から見てまとめて計上した印の個数(図の黄色の )を数える(imos法などで高速にできる)
を見て刀の動きを決定し,答えにする
とすればよいです。
以上を実装して,1REの末にAC。
前に0が連続するときは最初に省いてから以上のアルゴリズム をやるというようにしていたのですが,assert(ans.size() == n); と書いてしまったのがまずかったです。先頭に連続する0を省いてnが変わっているにもかかわらず,そのままassertをしてしまいました。ただ結局これ以外にもバグっている箇所を見つけられたので結果的には良かったです。
assertつけて提出するのは,バグ発見においてとても効果的ですが,嘘のassertをしないようにだけ気を付けようと思いました。
K
最後に残ったKを考え続けます。ほとんど全チームが通しているので,実はめちゃくちゃ簡単なんじゃないかと,Bの位置の偶奇とかMの偶奇とかでごちゃごちゃ実験をしてみますが,全く合わず。グランディ数を考えると0,1,2しかないのか,いやこれは任意の局面 に対して なんだから で当たり前じゃんとかやっていました。2次元平面上で考える当初の解法も再検討してみますが, から全く落ちません。結局K問題を解くことはできませんでした。
6完16位でした。
day3結果
コンテスト後
Kは2次元平面上の方針で合っていたようです。ただ,斜めで同じになることにはまったく気づきませんでした。こういうのは気づかないときは本当に気付かないものだと実感しました。
Fがここ1年で解いた問題の中で一番面白かったと言えるくらいいい問題だったので,チームメイトと後輩にこの感動を共有するなどしていました(そのため本記事でも,このFのパートが一番長いです)。とにかくとても気持ちのいい問題でした。
おわりに
開催にあたり準備してくださったスタッフのみなさん,交流してくださった参加者のみなさん,本当にありがとうございました。Yokohamaでは良い結果を残せるように日々精進していきます!