AHC 034 / ABC 359 / Floor Sum

Jun 23, 2024

AHC 034

AHC 034 に参加しました。グリッドに +-100 の値が設定されており、効率良く 0 に均す問題でした。いつも通り適当にパスを作ってうろちょろすることしかできず、成長の仕方が分かりません。

yosupo さんが 1 位でした。かっこいい。 トヨタ自動車プログラミングコンテスト2024#6(AtCoder Heuristic Contest 034) 解法 - よすぽの日記

ABC 359

ABC 359 に参加しました。

問題A 問題B 問題C 問題D 問題E 問題
予想103001,3001,1001,500
実際102782813811275
Table 1: Diff 予想

A 問題

なんと文字検索の問題でした:

main=interact$show.length.filter(=='T')

B 問題

i[0,2n3]i \in [0, 2n - 3] の範囲で見ます:

main=interact$show.sum.f.map read.words;f(n:x)=[1|i<-[0..2*n-3],x!!i==x!!(i+2)]]

zipWith を使うが上手い方法ですね:

C 問題

無理過ぎる……

D 問題

直前 K 文字の状態を持って DP します。 KK 文字目までと KK 文字目以降で処理が変わるのが厄介なところです。

E 問題

ii 番目の壁を乗り越えるためには、 ji(k[j,i],A[k]A[i])j \le i (\forall k \in [j, i], A[k] \le A[i]) を高さ A[i]A[i] まで埋める必要があります。また j1j - 1 番目の壁を乗り越えるためには、 dp[j1]dp[j - 1] の水が必要です。

DP 解法 1. [x] 区間 Max の 2 分探索

Max モノイドのセグメント木を持ちます。上記の jj を 2 分探索によって発見します。計算量は O(Nlog2N)O(N \log^2 N) ですが、簡単で良い方法だと思います。

DP 解法 2. [ ] 区間 Max を伸ばしていくモノイド

区間 Max をどこまで伸ばしていけるか判定するためのモノイドを作成します。計算量は O(NlogN)O(N \log N) ですが、真横のモノイド同士しか結合できないなど、細かい判定がかなり大変です。

DP 解法 3. [ ] 遅延伝播セグメント木を用いたシミュレーション

同様に、重い方針だと思います。

別解. [ ] スタックを持ってシミュレーション

なるほど。ここでスタックは償却計算量 O(1)O(1) (1 回のクエリで最大 1 つのみ要素が追加され、それぞれの要素は最大 1 度のみ使用される) であり、 O(N)O(N) で解けるみたいです。

F 問題

難しそうですが……。

Floor sum (unsigned)

AtCoder Library Practice Contest (ALPC) を埋めました。最後に残った問題が floor_sum でした。 floor_sum の解説は、 【math編】AtCoder Library 解読 〜Pythonでの実装まで〜 が良かったです。この記事の図を写経して理解しました。

以下は 100% 自分用のメモです。

TODO: 誤記修正 (多数)

概要

Floor sum は 1 次関数 y(x):=ax+bm(a,bZ0,mN)y(x) := \frac {ax + b} {m} (a, b \in \mathbb{Z}_{\ge 0}, m \in \mathbb{N}) の下側にある格子点の数を高速で数えるアルゴリズムです。具体的には、 S:=[(y,x)x[0,n),y(0,y(x)]]S := [(y, x) | x \in [0, n), y \in (0, y(x)]] 中の格子点の数を O(logaN)O(\log aN) で求めるアルゴリズムです。

考察

SS を 3 つに分けて計算します。

S1,S2S_1, S_2

2024-06-23-floor_sum_1.webp

S3S_3

残る S3:=[(y,x)x[0,n1],ambm<yy(x)S_3 := [(y, x) | x \in [0, n - 1], \lfloor \frac {a} {m} \rfloor \frac {b} {m} \lt y \le y(x) を考えます。 y:=ybmy' := y - \lfloor \frac b m \rfloor にフォーカスします。このとき元の直線は y=am+b%mmy' = \frac {a} {m} + \frac {b \% m} {m} となります。

2024-06-23-floor_sum_2.webp

実はダルマ落としのように赤い三角形部分を切り落としてしまっても、 S3S_3 からの格子点の数は変わりません。元の直線の傾きから赤い直線の傾きを引き、青い直線 y=a%mm+b%mm:=y(n)y = \frac {a\%m} {m} + \frac {b \% m} {m} := y'(n) が現れます::

2024-06-23-floor_sum_3.webp

ここで x,yx, y'x,yx'', y'' の関係を整理します。

x=y+ny=x+y(n)\begin{aligned} x &= -y'' + n \\ y' &= -x'' + \lfloor y'(n) \rfloor \end{aligned}

これを肝心の直線 y=a%mmx+b%mmy' = \frac {a\%m} {m} x + \frac {b\%m} {m} に代入して整理します:

x+y(n)=a%mm(y+n)+b%mm=a%mmy+a%mmn+b%mm=a%mmy+y(n)a%mmy=xmy(n)+my(n)=x+m(y(n)y(n))=x+((a%m)n+b%m)%my=mx+((a%m)n+b%m)%ma%m\begin{aligned} -x'' + \lfloor y'(n) \rfloor &= \frac {a\%m} {m} (-y'' + n) + \frac {b\%m} {m} \\ &= - \frac {a\%m} {m} y'' + \frac {a\%m} {m} n + \frac {b\%m} {m} \\ &= - \frac {a\%m} {m} y'' + y'(n) \\ \frac {a\%m} {m} y'' &= x'' - m \lfloor y'(n) \rfloor + m y'(n) \\ &= x'' + m (y'(n) - \lfloor y'(n) \rfloor) \\ &= x'' + ((a\%m)n + b\%m)\%m \\ y'' &= \frac {mx'' + ((a\%m)n + b\%m)\%m} {a\%m} \end{aligned}

上図の x,yx'', y'' 座標系に注目します。紫部分を除去し黄色部分をくっつけることで、元の問題と同じ形に帰着できました。よって floor_sum は再帰的に計算できます:

2024-06-23-floor_sum_4.webp

計算式

floorSum(n,m,a,b)={0 if m=0,0 if a=0,fromS1+fromS2+floorSum(n,m,a%m,b%m) otherwise.floorSum(n,m,a,b)={0 if m=0,0 if a=0,fromS1+fromS2+floorSum(n,m,a%m,b%m) otherwise.\begin{equation} \mathcal{floorSum}(n, m, a, b) = \begin{cases} 0 \text{ if } m = 0, \\ 0 \text{ if } a = 0, \\ \mathcal{fromS_1} + \mathcal{fromS_2} + \mathcal{floorSum}(n, m, a\%m, b\%m) \text{ otherwise.} \end{cases} \end{equation}

実装

再帰関数を書きました: Math/FloorSum.hs.

末尾再帰 & 正格評価の形 (返値を !acc 引数として引き回す形) に書き直しても、ジャッジ環境においては高速化されませんでした。 Haskellで再帰を心置きなく書いて良い理由 を見る限りでは !acc を書いて初めてサンクを潰せるように見えますが、実はコンパイラがよしなに正格評価してくれる気がします。関連: foldl vs. foldl'に終止符を打つ

計算量

傾きが大きいほど計算量が増え、 O(logma)O(\log ma) らしいです。たぶん互助法の計算量と同じ? 今は理解する体力が残っていません……。

感想

消耗しました。この程度の数学に 1 週間を丸ごと費やすとは……絶望ですw

Floor sum (signed)

上記の floorSum において a0,b0a \ge 0, b \ge 0 の成約を取り外すことができます。応用的な問題で役立つらしく、これも読み込みたいです。

4.9. a, bが負の場合