概要
数列 に対し、その gcd 畳み込み*1 を以下のように定めます
この畳み込みの計算方法と、応用として AGC038-C を解く手順を示します。
数列の変換で要素毎の乗算に変換
数列から数列への関数 を導入します。
は倍数の和を取る変換で、以下のように定義します。
高速ゼータ変換を知っている人なら、上位集合に対する高速ゼータ変換の類似物と言えば分かりやすいかもしれません。
実は、 と の gcd 畳み込み に対して以下の式が成立します。
よって の逆変換 が存在すれば
( は要素毎の乗算)
によって を計算することが出来ます。
このような変換を考えることは、 に限らず や による他種の畳み込みにも共通しています。
変換の概説
が成立しているとき、 かつ です。
条件を緩めて、 かつ なる について ならば容易に計算することが出来ます。
これは が独立なので となるためです。
このような について は必ずしも成立しませんが、 が成立します。
これをよく見ると、 に等しいことが分かります。
を で計算する
・
各 について なる を全て足しても、調和級数により計算量は となります。
void f(std::vector<int> &a) { int n = a.size(); for (int k = 1; k < n; ++k) { for (int i = k * 2; i < n; i += k) { a[k] += a[i]; } } }
を考える都合により、in-place な実装例を示しました。
値を重複して足し合わせないよう、 のループは昇順に回す必要があります。
・
のアルゴリズムをそのまま逆順になぞればよいです。
同様に、 の昇降に気を付けてください。
void inv_f(std::vector<int> &a) { int n = a.size(); for (int k = n - 1; k >= 1; --k) { for (int i = k * 2; i < n; i += k) { a[k] -= a[i]; } } }
のループは並列なのでどちらに回しても大丈夫です。
AGC038-C を解く
数列 を として、 と を gcd 畳み込みしたものを とします。
であるため、基本的には
を計算すればよいです。
ただし に反する が足されていることに注意して、補正します。
以上を実装した提出がこちらです。
Submission #7659496 - AtCoder Grand Contest 038
先頭に貼ってある modint 構造体については以下の記事を参照してください。
modint 構造体を使ってみませんか? (C++) - noshi91のメモ
関連する文献
gcd に限らず、ゼータ変換とメビウス変換全般を説明した丁寧で分かりやすい記事です。
更なる高速化
こちらのコードなどを参照してください。
*1:正式名称が分かっていません、ご存知の方がいらっしゃいましたら教えてください