ABC 353 に参加しました。何とかレーティングをキープできました。
y+0 のように +0 を入れると read が整数型に推論される技を知りました:
main=interact$show.f.map read.words;f(_:h:x)=head$[i|(i,y)<-zip[2..]x,y+0>h]++[-1]
% 演算子を定義し、関数呼び出し時の空白の数を減らしましょう:
main=interact$show.(0%).tail.map read.words
a%[_]=min 1a
a%(k:y:r)|a+y>k=1+0%(k:y:r)|0<1=(a+y)%(k:r)
A をソートします。 mod と言わず 108 を引くことにすれば、一括処理できます。
i を固定したとき、和が 108 を超え始める点を見つけるためには 2 分探索または尺取り法を使います。 Haskell で実装する尺取り法の必勝法……を考え始めることの検討を加速します。
ゴリ押しするか簡単な解法を考え抜きます。
私はゴリ押ししました。日記もゴリ押しに一塩です。
これは輪を通る弦の組み合わせの数をカウントする問題だと思いました。先頭の文字でグループ分けしつつ、 1 文字ずつ剥いて行けば解けます。
Figure 1: 貪欲に数えて行けば良いなお文字列ソートの計算量は O(NL1(logN+logL1)) 程度 らしいです 。今回は文字列長の総和 L≤3⋅105 のため N=L1=L とすれば O(LlogL) ? 速いですね〜 (読もう……)
EDPC - Z と同じ問題に見えました。絶対値を外すため、 CHT に 2 本ずつ直線を追加していけば解けるはずです。 CHT は未習得の高度典型なので解けませんでした。
ところが絶対値を外して現れた式は、単なる 2 つの Max であってセグメント木 2 本に載ります。解けるべき問題でした。
なお CHT に似た何かがある? みたいなので、 CHT に挑む時までに gksato さんの提出を読み込んでみようと思います。
高度典型: FFT/NTT/Convolution の勉強ノートです (未実装) 。
内容は誤りだらけです 。真剣に読まないでください。
まずはフーリエ変換をエアプします。 フーリエ解析―基礎と応用 を参考にしました。
ボトムアップかつ天下り的に行きます。
三角関数のように無限回微分できる関数は、冪級数の形に展開できることが知られています:
f(x)=n=0∑∞n!f(n)(x0)(x−x0)nx0=0=n=0∑∞n!f(n)(0)xn三角関数と指数関数を x0=0 の回りで冪級数展開すると、オイラーの公式 eix=cosx+isinx が得られます。
三角基底 (複素三角基底?) {eix} は完全直行基底であり、ある種の f(x) が三角基底の線型結合で表せることが知られています。
フーリエ変換の基本は周期関数です。周期 T の周期関数 f(x) は周期 T,2T,… の三角基底の線型結合で表せるものとします。係数 cn を複素フーリエ級数と呼びます:
f(x):∼n=−∞∑∞cneiT2nπx=n=−∞∑∞cneiωnx三角基底は直行基底であり、基底に対する成分 (フーリエ級数 cn) は内積に比例します:
(f(x),eiωx)=(cneiωx,eiωx)=cn(eiωx,eiωx)=cn∥eiωx∥2=cn∫−2−T2Teiωxe−iωxdx=cnTcn を f(x) に代入すると以下の形になります:
f(x)=n=−∞∑∞T1(f(x),eiωnx)eiωnx
T=Δω2π→∞ とすれば、非周期関数 f(x) を連続な基底ベクトル {eiωx}ω で展開できます (逆フーリエ変換 (IFT)) 。
f(x):=n=−∞∑∞2πΔω(f(x),eiωnx)eiωnx=∫−∞∞2πdω(f(x),eiωx)eiωx=∫−∞∞2πdωF[f](ω)eiωxフーリエ変換 F[f](ω):=(f(x),eiωx) は f(x) の eiωx 成分に相当します。指数関数の微積分は簡単なので、 {eiωnx} を基底としたのは幸先良さそうです。
デジタル信号処理へ寄ります。ここからは ビギナーズ デジタルフーリエ変換 および小野測器の 計測コラム の em138 添付資料 を参考にエアプします。
関数 f(x) を τ の間隔で離散化し、離散関数 fτ(x) を得ます。 f(x) に周期的デルタ関数 δτ(x) をかけることで、 f(x) の離散化を表現します:
δτ(x)fτ(x):=n∈Z∑δ(x−nτ):=δτ(x)f(x)fτ(x) のフーリエ変換も離散化されています:
F[fτ](ω)=(fτ(x),eiωx)=∫−∞∞fτ(x)e−iωxdx=n∈N∑f(nτ)e−iωnτさらに時間が離散化されたことから位相に周期性が生じており、 F[fτ(x)](ω) は周期 τ2π の周期関数になりました (F[fτ](ω)=F[fτ](ω+τ2π))。
改めて fτ(nτ) を三角基底で展開すると、次の式を得ます:
fτ(nτ)=∫−τπτπ2πdωF[fτ](ω)eiωnτ時間信号 x(t) の一部を時間幅 T で切り取り、 τ の間隔で離散化したとします。この信号列を周期 T の周期関数に拡張すると、やはり複素フーリエ級数の和の形に展開できます。
x(kτ)=n∈Z∑cneiωnkτ=n∈Z∑cneiT2nπkτn∈Z とありますが、時間が離散化されたことによって位相が周期的になり、 x(kτ) は {eiωnx}n∈[0,N−1]∩Z のみにより展開されるはずです。実際 n→n+rN に分解して確かめられます (天才だ……):
x(kτ)=n∈[0,N−1]∩Z∑r∈Z∑cn+rNeiωn+rNkτ=n∈[0,N−1]∩Z∑eiωnkτr∈Z∑cn+rNcn=0(∣n∣>N)=n∈[0,N−1]∩Z∑eiωnkτcn上記の 2 ~ 3 行で帯域制限を設け、 x(t) が N 次の高調波成分までしか持たないとした場合、 ∑r∈Zcn+rN=cn から非常に簡素な式に整理できました。 N 個 (N=τT) の信号 {x(nτ)}n を整理すると、以下の行列で書けます:
x(0)x(τ)⋮x(TT−τ)=11⋮11eiω1⋮eiωN−1………1eiω1(N−1)⋮eiωN−1(N−1)c0c1⋮cN−1よって時間信号 x(t)x(t)x(t) に対して 1. その一部を時間幅 TTT で切り抜き 2. τ\tauτ の間隔で離散化し 3. 周期 TTT の周期関数に拡張し 4. 帯域制限を行うと、 N 回のサンプリング結果から複素フーリエ級数が分かり、 AC の新作が発表されます。
上記の x(nτ):=xnx(n\tau) := x_nx(nτ):=xn に対し、なぜか改めて離散フーリエ変換 XkX_kXk を定義します (なぜ……?):
Xk:=∑n∈[0,N−1]∩Zxne−i2πNk:=∑n∈[0,N−1]∩ZxnWnk\begin{aligned}
X_k &:= \sum_{n \in [0, N - 1] \cap \mathbb{Z}} x_n e^{-i \frac {2\pi} {N}k}
\\ &:= \sum_{n \in [0, N - 1] \cap \mathbb{Z}} x_n W^{nk}
\end{aligned}Xk:=n∈[0,N−1]∩Z∑xne−iN2πk:=n∈[0,N−1]∩Z∑xnWnkこの気持ちは勉強不足のため理解できていません。フーリエ変換の方が重要なので、単純な式にしたかった?
DFT の高速計算 (FFT) を小野測器の 計測コラム emm140 号用 から学びます。 P2 の図から汲み取れる通り、 8 点 DFT の出力 {X8,k}k∈[0,7]\{X_{8, k}\}_{k \in [0, 7]}{X8,k}k∈[0,7] は、それぞれ 4 点 DFT の和に分解できます:
X8,k({xk}k∈[0,7]∩Z)=∑n∈[0,7]xnWnk=∑n∈[0,2,4,6]xnWnk+∑n∈[0,2,4,6]xn+1W(n+1)k=∑n∈[0,2,4,6]xnWnk+Wk∑n∈[1,3,5,7]xnWn=∑n∈[0,2,4,6]xnWnk+Wk∑n∈[1,3,5,7]xnWn=X4,k(x0,x2,x4,x6)+WkX4,k(x1,x3,x5,x7)\begin{aligned}
X_{8,k} (\{x_k\}_{k \in [0, 7] \cap \mathbb{Z}}) &= \sum_{n \in [0, 7]} x_{n} W^{nk}
\\ &= \sum_{n \in [0, 2, 4, 6]} x_{n} W^{nk} + \sum_{n \in [0, 2, 4, 6]} x_{n+1} W^{(n+1)k}
\\ &= \sum_{n \in [0, 2, 4, 6]} x_{n} W^{nk} + W_k \sum_{n \in [1, 3, 5, 7]} x_{n} W^{n}
\\ &= \sum_{n \in [0, 2, 4, 6]} x_{n} W^{nk} + W_k \sum_{n \in [1, 3, 5, 7]} x_{n} W^{n}
\\ &= X_{4,k}({x_0, x_2, x_4, x_6}) + W_k X_{4,k}({x_1, x_3, x_5, x_7})
\end{aligned}X8,k({xk}k∈[0,7]∩Z)=n∈[0,7]∑xnWnk=n∈[0,2,4,6]∑xnWnk+n∈[0,2,4,6]∑xn+1W(n+1)k=n∈[0,2,4,6]∑xnWnk+Wkn∈[1,3,5,7]∑xnWn=n∈[0,2,4,6]∑xnWnk+Wkn∈[1,3,5,7]∑xnWn=X4,k(x0,x2,x4,x6)+WkX4,k(x1,x3,x5,x7)8 点 DFT の出力を以下の信号流れ図にまとめます。 {xn}n\{x_n\}_n{xn}n が上下に 2 分割されており、再帰的に O(NlogN)O(N \log N)O(NlogN) で計算できることが予想できます。

バタフライ演算の部分を丁寧に図示すると以下です。黒点を接続とし、加算器と乗算器を明示しています:
Figure 2: 信号流れ図が読めなかったのでこれを簡略化し、また W84=eiπ=−1W_8^4 = e^{i \pi} = -1W84=eiπ=−1 を代入すると、前の図になります。読めないよ〜〜
8 点 DFT を再帰的に展開すると、 x0,..,x7x_0, .., x_7x0,..,x7 は x0,x4,x2,x6,x1,x5,x3,x7x_0, x_4, x_2, x_6, x_1, x_5, x_3, x_7x0,x4,x2,x6,x1,x5,x3,x7 の並びになります:

したがって以下の手順で高速に DFT を計算できます。
- 添字の置換を (一括して) 行う
- バタフライ演算を繰り返し適用する
人が数列をソートするときは、最も大きな位から順にソートすることが多いです。 (2-radix) FFT においては (2 進数表記で) 小さな位から順番にソートされていくことになります。したがって添字をビット反転 (例: 0b1100 -> 0b0011) した値を基準に {xn}n\{x_n\}_n{xn}n を昇順ソートしたことになります。
Figure 3: 2-radix FFT における引数の置換の追跡 (検索すれば同じ図が出てきます)バタフライ演算の実装にあたり、 WnkW_n^kWnk をどう計算するか。ここで FFT は精度が悪いらしい ので、競プロでは eiωne^{i\omega_n}eiωn でなく mod 998244353\bmod 998244353mod998244353 の世界で直行基底を定義してフーリエ変換を行うようです。
くう〜疲れましたw まだ道半ばです。たぶん実装は遅延セグメント木より簡単そうかな……