G は i 回目の移動で頂点 u にレベル X で来る確率が p だとすると、知りたいのはすべての i と」 これがヒントであって答えでないのは、これを読んでさえさっぱり遷移が書けなかったから。何度も何度もガチャガチャやって考えてガチャガチャやって考えてお風呂に入って考えた。提出 #36502350 (TLE×13)。書けなかったのは 29 行目と 30 行目と 32 行目。それで苦労の結果が TLE。計算してみたら N,M,K≦3000 で Ds.size=3 で E.sum(&:size)=2×M で C0.size+C1.size=N だからループの回数が K×(3×2×M+N) = 6300 万。Ruby は 1000 万回を超えたあたりから厳しくなってくるのでこのままでは無理だよ。C_u=1
なる u についてpX^2
の和。(i,u) を状態として\sum pX^2
を管理しようとすると、レベルアップの処理が\sum pX^2\rightarrow\sum p(X+1)^2=\sum pX^2+2\sum pX+\sum p
となる。よって\sum p
、\sum pX
、\sum pX^2
を管理しておけば更新も含めて計算できる。
sumf
変数をこの式にしたい。最初の提出の時点で考えなかったことはないけど、全然まとまらないねってそのときは思った。HI/LO を入れ替えた2つの和を左からと右からでまとめてるっぽい?■D 問題「XOR Tree Path」。最初はビット列を掛け合わせて寄せていく解法(名前がわからん)かと思ったけどだんだん木 DP が見えてきた。葉ノードから寄せて上げていく。記録するのは、反転操作を親に伝播させるときに部分木がいくつの黒ノードを含むかと、そうでないときにいくつの黒ノードを含むか。木 DP の肝は子ノードのマージ部分だけど、ここに罠があった。子ノードが1つしかないときに「反転操作を親に伝播させるときに部分木がいくつの黒ノードを含むか」という数字を反転しない場合の数字に反映させる方法はない。反転操作を打ち消し合う他の子ノードが存在しないから。提出 #36339752 (AC / 602 Byte / 286 ms)。■E 問題「Name Value」。制約が、特に A,B 文字列の長さがやばすぎるだろうと思ったけど、クエリ文字列のスキャンが前方/後方から貪欲に行えるので、それを効率的にやるために準備を頑張ればいい。文字種×(A,B 文字列の長さ) = 2000 万要素くらいの遷移表を用意して許されるのか考えたよね。うーん、ありかなあ。でも1要素ずつスクリプトでコピーするとダメかも。提出 #36341147 (AC / 509 Byte / 1309 ms)。許された。最初は遷移表に添字を記録していたけど、遷移先の遷移表を持たせるのがより直接的ではないかと気がついてそのようにした。人間が脳みそに余計なステップを持っているとコンピュータにも無駄な回り道をさせがち。こういう無駄を詰める思考はコードゴルフと通じている。提出 #36348178 (AC / 392 Byte / 1216 ms)。Array#dup メソッドの呼び出しを省いた(20201207)のと遷移表のサイズを半分にしたのと無駄な Array#min を省いたのと添字を記録していたなごりの無駄な変数を省いた。遷移表のサイズを半減した引き替えに整数の引き算がループの中に4回出現している。メモリ大量確保&コピーとちょっとした整数演算ではどちらが時間を食うかという判断。平均的に速くなったけど重いケースではそれほど変わらず。1度に1要素しか更新しない遷移表を毎回丸々コピーするのがもったいないんだよなあ。空間がではなく時間が。でもたぶんそれをやめると探索が必要になって log が付くのでは? インチキみたいなうまい方法があればなあ。遷移表の行と列を入れ替えるみたいな方法はたぶんうまくないんだよなあ。2要素の遷移表……。■F 問題からは解けません。■G 問題「Count Arithmetic Progression」を考えてる。傾きに注目するにしても直線の切片に注目するにしても 10 の 12 乗以下という制約がネックになって部分点しか得られない(答えが違えば部分点ももらえないが)。注目するなら 30 万以下の要素しかない L,R 整数列になるのかなあ。それで解く方法が見えないけども。@2022-11-11 部分点をもらいました>提出 #36390042。部分点の制約でも青 diff は下らないと思うなあ。探索できる要素ってある? 1ずつ計算せずに済むような単純な比例関係があったりする? どっちも(見つから)ないんだよなあ。DL,DU 変数を賢いデータ構造で仮想化して妥当な計算量で求められるとしたらどう? DU,DL 変数は傾きの上限下限だから傾きを制約する数字が連続的に変化していったら結局 10 の 12 乗に比例したステップが生じると思う。まとめてひと束で計算できる状態の単位が見えない。数列の要素数に比例したステップしか生じなかったりする? じゃあ傾き制約の変化点を順に知る方法は。■@2022-11-16 解説を読んだ。Convex Hull Trick の名前を知った。「Convex Hull Trickを知っていますか?僕は知っています。 - Qiita」「傾きの単調性が要らない Convex Hull Trick - kazuma8128’s blog」 読んだ。直線をソートして順番に交点を求めて上限/下限の変化が知れるらしい。蟻本で既出だったことも知った(初版 286 ページ K-Anonymous Sequence)。しかし書けない。0=a1<...<an=M-5
や 5=a1<...<an=M
など、初項 a1 が取り得る値が 0 から 5 の 6 通り考えられるのでこれを掛ける。困ったのは、+3 操作の上限を決めて 0 個、1 個、2 個と挿入する場合の数の合計も、+1 操作の上限を決めて 0 個、1 個、2 個と挿入する場合の数の合計も事前に累積和を計算しておくことで定数時間で求められるのだけど、初項が取り得る値のバリエーションを累積和に掛ける方法がわからなかった。たとえばメインループで +3 操作の回数を決め打ったとする。追加で可能な +1 操作回数の上限がわかるので累積和を引く。しかし +1 操作回数が 0 回のときに初項が取り得る値の種類数と 1 回のときに取り得る種類数は異なる。+1 操作の選び方に階段状の倍率を掛けたものの累積和が欲しい。倍率はスライド式であり、×5,×4,×3 かもしれないし、×3,×2,×1 かもしれない。あるいは初項の前へのプラス操作が他と統一的に数えられたら。困ったときはお風呂で考える。普通の平坦な累積和と階段状の累積和を組み合わせればいける。いけなかったのは、N,M の制約上限が普段より厳しい 10 メガなのであり、コンビネーションの事前計算に加え累積和を2本も作成する 3N の処理で制限時間の4秒を超えてしまったこと。10 秒まで実行されるコードテストで 4.4 秒からなかなか縮まなかった。■提出 #36313584 (AC / 773 Byte / 3938 ms)。C1 メソッドの値から A 数列を作成するときに呼び出し回数を半分に節約したり、メソッドの中身をインライン展開したり、メインループの共通項を ICI として括り出したり、fn 数列の前部を切り捨てて添字のオフセット計算を省略したり、いじましい努力の結晶ですよ。Ruby によるすべての提出を見ると、tinsep19 さんの提出は最悪ケースこそほぼ同じタイムだけど、内訳を見ると1秒早かったり倍早かったりするものが多い。4秒ぎりぎりの最悪ケースの位置が異なってるのが面白い。向こうの最悪ケース(1つだけ)はこちらの最悪ケース(13 個)ではないのだ。■メインループを逆順にすると必要なのが累積和の末項だけになって事前の作成が不要になる気がするなあ。■提出 #36318378 (AC / 736 Byte / 3061 ms)。メインループを逆順にして累積和が配列2本から変数2個になった。*smallN* ケースだけやけに遅くて最適化の余地があるみたいだけど、それ以外は概ね良好かな。汚いという意味では良くないけど。■「スナネコ「ABC276Gの解説に追記しました」 https://t.co/9pw10g1SOS https://t.co/bRGlSd8RcW」。異次元からの眺め。さっぱりすぎる。(1+2+...+N-1)/N <= X/Y = (1+2+...+N-M)/N < (1+2+...+N)/N)
以前はそもそも上と下の両方を抑えようという意識が働かなかったと記憶している。雰囲気で片方を抑えてそれで?って感じ。ぼんやりしてら。■しかし罠が2つ。提出 #36180781 (WA×8)。考えられるすべての N と M を N の昇順に出力することが求められていたのに1組しか出力していなかった。提出 #36180916 (WA×5)。分子と分母にまったくどうでもいい数が共通に掛けられているケースに対処できていなかった。「ただし、入力は既約分数とは限らない」とはそういう意味。約分している場合もそのままの場合もあるというだけではなく、無駄に数字をふくらませている場合があった。■提出 #36181139 (AC)。テストケースがあればデバッグが捗っただろうけど、古い ARC のは公開されていない。
直前の要素から連続するのかそうでないのかでコスト計算が変わってくる」ことがわかっていても「
直前の要素を使用しているかをフラグとして持ってあげるとよい」と明示されることが問題解決のヒントになるのだなあ。■提出 #36122711 (TLE×4 / 2191 ms) のち 提出 #36123362 (AC / 1864 ms / 107396 KB)。Ruby によるすべての提出を見ると他の2つの AC 提出と比べてメモリ使用量が5倍以上ある。なんか違うことをやってんだろか。■提出 #36124108 (AC / 1452 ms / 45264 KB)。メモリ使用量の差は Hash か Array かの違いだと思ってまねしてみたけど、時間もメモリもなんか違う。それに AC 提出の片方のメモリ使用量を1桁読み間違っていたこともわかった。18 メガじゃなくて 180 メガだから自分のと大差なかった。だいたい同じことをやってるよ。
オリジン間 HTTP リクエストのリスクの緩和に役立てています」とはどういう皮肉か。■きっかけはこのツイートだったんだけど、「@chokudai AtCoder の API に Access-Control-Allow-Origin など CORS 指定がないのにはなにか理由があるのでしょうか? これがあれば、有志ウェブサイトが AtCoder の API を叩くためだけに Heroku などに依存する必要がなく、GitHub Pages などでの静的ホスティングが可能になると思うんですが…」、Heroku を利用することで何がどう可能になるのかがまだわかっていない。難しいね。Heroku の無料プランがなくなるんだっけ? そうすると有志ウェブサイトは何ができる?
t = (A+dx).fdiv dv
。これは「追いつく」関係のときに追い越してしまって幅 A の区間から出てしまうタイミングを計算する式。正しくは t = dx.fdiv dv
。2つめは 11 行目と 19 行目 でハッシュのキーに 0 を使っているところ。0 と 0.0 をソートしたら結果が不定になった(Ruby 2.7 の場合)。それではハッシュを入る方と出る方の2つに分けた甲斐がない。実数で閉区間は扱いにくいんよ(どうして区間を x+A 未満にしてくれなかったのか)。キーが 0 であれ 0.0 であれ入る方を必ず先に加算しなければいけないのにできていなかった。3つめは「追いつかれる」ケースの処理を忘れていたこと。■提出 #35903826 (AC / 712 Byte / 2942 ms)。これだけミスがあって時間もかかるなら解けた喜びしかない。なんにも惜しくない。■■■精進2。同じ ABC174-E 問題「Booster」(水 diff)。時間中は F 問題にかかりきりで読まなかった。ブースターを使う使わないを総当たりしてダイクストラ法かなと考えたりもしたけど、どうにもはまらない。むしろ巡回セールスマン問題(TSP)だった。■提出 #35902406 (AC / 674 Byte / 1847 ms)。これまで2問くらい TSP(roblem) を解いてるけど久しぶりすぎて忘れていた。ワーシャルフロイド法と同じ見た目をしていることを思い出すのに時間がかかり、何とか書き上げても TLE を2回食らった。以前にタイムを詰めに詰めてこれが決定版と言えるものを作ったんだけど、内容を覚えていないしどの問題かも思い出せなかった(そのときに競ってアイデアを提供してくれたアカウントは覚えている。精進中に見かけた名前を最近またコンテストで見るのは嬉しいものだけど、逆もあって寂しい)。唯一思い出したのが 12 行目の 1,0 = (N+M).times.partition{|b| 0<vs[b] }
。これで TLE が解消した。big |= 1<<k
とか big ^= 1<<k
になる。|=
や ^=
が =
、|
、^
を使ったシンタックスシュガーだとしたらそれも残念だけど(でもそうだよね?)、1 ビットに作用させるためだけに Bignum の一時インスタンスを作成して、桁数に比例したビット演算をさせているのが(たぶん)、もったいなくて仕方がない。リファレンスを漁るけど flip メソッドが見つからない。bitset みたいな特別な道具としてではなく普通の整数で多倍長のビット演算ができるところが、速度で不利になりがちな Ruby が強みにできるところであるので、機会を逃さず Bignum を酷使していきたいと思っている。これは昨日の日記(20221019)への言及。