Dirichlet 積と、数論関数の累積和

スポンサーリンク

Dirichlet 級数と Dirichlet 積

N={1,2,3,} を定義域とする関数 a,b:NC を、数論関数 (arithmetic function) と呼ぶことがあります。

c(n)=ij=na(i)b(j) により定まる c:NC を、a,bDirichlet 積といいます。この記事では、c=ab と表すことにします。この積は、交換法則・結合法則・分配法則などを満たします。

モノイド (N,×) に関する畳み込み演算と言ってもよいです。(N{0},+) の畳み込み演算は、形式的べき級数に置き換えることで性質が見やすく整理できることがありますが、(N,×) の場合には、Dirichlet 級数で表すことが一般的です。つまり、数列 {an}

Da(s)=n=1anns

に置き換えることで、c=abDc=DaDb と Dirichlet 積は級数の積に変換されます。

  • isjs=(ij)s を、分配法則を満たすように拡張することで積を定義します。
  • Re(s) が十分大きな複素数 s の代入を考える場合もありますが、今回は収束などの細かいことは無視しましょう
  • s」の符号は、歴史的な背景によりこうなっていますが、この記事では大事ではありません。式を書くのが手間であれば、全て「+s」にしてしまっても不都合は生じないでしょう。

また、無限数列を使って定式化をしますが、そのうち 1nN 部分だけを配列として持つのが基本的な取り扱いとなるでしょう。

Dirichlet 積の計算量

a, b が既知で、c=ab のとき、c(1),,c(N) を計算することは、O(NlogN) 時間で行えます。次の要領です。

c = [0] * (N+1)
for i in range(1, N+1):
  for j in range(1, N//i+1):
    c[i*j] += a[i] * b[j]

乗法的 Dirichlet 級数の場合

a(1)=1 かつ、互いに素な n,m に対して a(nm)=a(n)a(m) が成り立つような a は、乗法的であるといいます。素べきでの値 a(pk) からすべての a(n) が復元できるという性質で、約数の個数や和、Euler関数などの例が有名でしょう。

Dirichlet 級数の言葉で書けば、級数が Euler 積分解 Da(s)=p(ka(pk)pks) を持つことと対応します。

O(NloglogN) 時間での計算

a が乗法的であるとき、ab の計算は O(NloglogN) 時間 で行えます。a が疎な関数の Euler 積に分解されていることを利用して、in-place に p パートの乗算を繰り返します。Dirichlet 積の言葉で書けば、

for p in primes:
  D_{a,p}(s) = sum a(p^k) p^{-ks}  (p-part of D_a)
  D_b(s) ← D_b(s) D_{a,p}(s)

といった計算をしていけばよいです。

c = b
for p in primes:
  for i in range(N//p, 0, -1):
    n = p * i
    # n = (p^k) * m となるとき、c[n] ← c[n] + a[p^k] * c[m] として更新
    q, m = p, i
    while True:
      c[n] += a[q] * c[m]
      if m % p != 0:
        break
      q, m = q * p, m // p

in-place に更新するため、更新順に注意しましょう。大きい番号から更新することで、後の計算に必要な部分が先に上書きされてしまうことを避けています。計算量は、q=pkNNq=pNNp+O(N)=O(NloglogN) などと解析されます(q は素数べきで、p は素数)。

O(N) 時間での計算

c が乗法的ならば(特に a,b が乗法的ならば)、n が素数 p でちょうど k 回割り切れ n=pkm と書けるとき、c(n)=c(pk)c(m) が成り立ちます。このことを利用すると、c(1),,c(N)O(N) 時間で計算することもできます。

  1. 素数べき pkN に対して、c(pk) を愚直に(O(k2) 時間で)計算する。
  2. n=2,3,4, の順に、それが素数べきでないならば、n=pkm として c(n)=c(pk)c(m) により c(n) を求める。

前計算として、各 n を割り切る pk を持っておくことが必要になります。この前計算も、線形篩を用いることで O(N) 時間で行うことが可能です。

Dirichlet 積と累積和

数論関数 a に対して、A(n)=i=1na(i) と書くことにします。この記事では小文字と大文字を対応させて、a の累積和を Ab の累積和を Bc の累積和を C で表すことにします。

まず、次を確認しましょう。

【命題 1】c=ab とし、任意の 1iN に対して a(i),b(i),A(i),B(i) が計算されているとする。C(N)O(N) 時間で計算できる。

C(N)=n=1Nij=na(i)b(j)=ijNa(i)b(j) と式変形をします。さらに、M=N として、i,j=iM+i>M と変形します。

  • iM を固定します。ijNjN/i なので、この部分は a(i)B(N/i) と計算できます。
  • i>M であるとき、jM である必要があります。j を固定したとき、i の範囲は M<iN/j なので、b(j)(A(N/j)A(M)) が固定した j の寄与になります。

これらを足し合わせることで、C(N)O(N) 時間で計算できました。


N が大きいとき、この計算において、必要な a(i),b(i),A(i),B(i) のインデックスはかなり疎 (sparse) です。再び M=N とおきます。

【命題 2】c=ab とし、次の形のものがすべて計算されているとする。

  • 1iM となる i に対する a(i),b(i)
  • 1iM となる i に対する A(N/i), B(N/i)

このとき、C(N)O(N) 時間で計算できる。

計算方法は全く同じです。さらに同じようにすると、次も分かります。

【命題 3】c=ab とし、次の形のものがすべて計算されているとする。

  • 1iM となる i に対する a(i),b(i)
  • 1iM となる i に対する A(N/i), B(N/i)

このとき、次のものすべてが、合計 O(N3/4) 時間で計算できる。

  • 1iM となる i に対する c(i)
  • 1iM となる i に対する C(N/i)

計算する前後でデータの持ち方をそろえることで、同様の計算の反復に備えています。アルゴリズムはほとんど変わりません。

n=N/i と書ける n に対して、A(n/j) といった値が必要になりますが、n/j=N/ij が成り立つため、結局計算のいたる所で N/i と書けるようなインデックスしか登場しません。A(1),,A(M) の値は新たに必要となりますが、これは a(i) の累積和として簡単に手に入ります。

計算量を確認しましょう。

  • c(1),,c(M) の計算に、O(MlogM)=O(N1/2logN) 時間
  • C(N/i) の計算に、i ごとに O(N/i) 時間

となりますが、1M1tdt=O(M) であることなどから評価すると、合計で O(N3/4) 時間であることが分かります。


n で区切るという発想で自然に O(N3/4) という計算量が出てきましたが、実はこの計算量は簡単に落とすことができます。

【命題 4】NKL となるように K,L をとる。c=ab とし、次の形のものがすべて計算されているとする。

  • 1iK となる i に対する a(i),b(i)
  • 1iL となる i に対する A(N/i), B(N/i)

このとき、次のものすべてが、合計 O(KlogK+(NL)1/2) 時間で計算できる。

  • 1iK となる i に対する c(i)
  • 1iL となる i に対する C(N/i)

特に K=(N/logN)2/3, L=N1/3(logN)2/3 ととった場合には、O(N2/3(logN)1/3) 時間である。

また、この時間計算量は、以下の条件のように削減可能である。

  • a,b のうち一方が乗法的であれば、O(N2/3(loglogN)1/3) 時間
  • a,b の両方が乗法的であれば、O(N2/3) 時間

C(N) ひとつ計算する場合には N1/2 で切るのがオーダー最適でしたが、多くの値をまとめて計算する場合には、N2/3 程度で切ることでオーダーが改善できることになります。

アルゴリズムは同様で、

  • c(1),,c(K) を、O(KlogK) 時間で計算する。
  • A(1),,A(K) および B(1),,B(K)O(K) 時間で計算する。
  • 1iL に対して、C(N/i) を、i ごとに O(N/i) 時間で計算する(【命題 2】)。1iL に対する合計時間は O((NL)1/2) 時間である。

となって、O(KlogK+(NL)1/2) 時間であることが分かります。

また a,b がともに乗法的であれば、Dirichlet 積の計算がより高速に行えて、上記の計算が全体で O(K+(NL)1/2) 時間であることも分かります。この場合には K=N2/3, L=N1/3 ととれば、O(N2/3) 時間が実現できますね。

除算の計算

同じ議論をうまく遡ることで、次の操作も行えることが分かります。a10 が必要になります。

【命題 5】NKL となるように K,L をとる。ab=c とし、次の形のものがすべて計算されているとする。

  • 1iK となる i に対する a(i),c(i)
  • 1iL となる i に対する A(N/i), C(N/i)

このとき、次のものすべてが、合計 O(KlogK+(NL)1/2) 時間で計算できる。

  • 1iK となる i に対する b(i)
  • 1iL となる i に対する B(N/i)

特に K=(N/logN)2/3, L=N1/3(logN)2/3 ととった場合には、O(N2/3(logN)1/3) 時間である。

また、この時間計算量は、以下の条件のように削減可能である。

  • a,c のうち一方が乗法的であれば、O(N2/3(loglogN)1/3) 時間
  • a,c の両方が乗法的であれば、O(N2/3) 時間

【命題 4】の類似ですね。【命題 1】で出てきた式変形などを使って、小さな b(i), B(i) から順に計算していくことになります。例えば、Dirichlet 積の定義式から c(n)=a(1)b(n)+より小さな b(i) の寄与 と書けることから、b(1),b(2),,b(K) が計算できます。累積和をとれば B(1),,B(K) が計算できます。C(n)=a(1)B(n)+より小さな B(i) の寄与 と書けることから、B(N/i) も小さい方から(i が大きい方から)順に計算できます。

詳しい実装は紹介しませんが、後述の関連問題を解きながら細部を考えてみてください。

【命題2】の類似は成り立ちません。つまり、1 点での値 C(N) だけが欲しい場合にも、大きな N での C(N) の計算に小さな C(i) の値が必要となるため、O(N2/3+ϵ) の形の計算量になります。

具体例

代表的な Dirichlet 級数として、ζ(s)=n=1ns があります。また、この平行移動 ζ(sk)=n=1nkns も頻出でしょうか。これらの組合せだけでも、有名な数論関数がかなり網羅できてきます。

約数関数の累積和

σk(n)=d|ndk としましょう。特に、k=0 ならば約数の数え上げ、k=1 ならば約数の和です。Dirichlet 級数は

n=1σk(n)ns=ζ(s)ζ(sk) と表すことができます。

ζ(s)ζ(sk) については、n 番目の取得や、n 番目までの累積和の取得は、k を定数と見た場合 O(1) 時間で計算できます。したがって、その N 番目までの総和は、【命題 1】より O(N) 時間で計算できます。

この例は、結局のところ ijNik の計算を、少なくとも一方は N 以下であることに注目して計算しているだけで、きわめて基本的です。これまでの命題について、簡単な場合にロジックを見直したいときに役に立つのではないでしょうか。

N/i 番目までの総和をすべての i に対して列挙したい場合には、【命題 4】より O(N2/3) 時間で計算できます。

Möbius 関数の累積和(Mertens 関数)

Möbius 関数 μ(n) の累積和 M(n)=i=1nμ(i) は、Mertens 関数 と呼ばれます。Möbius 関数は Dirichlet 積においてはとても基本的で、ζ(s) の逆元に相当します:1ζ(s)=n=1μ(n)ns.

【命題 5】を用いることで、Mertens 関数 M(N)O(N2/3) 時間で計算できることが分かります。

Euler 関数の累積和

Euler 関数 φ(n) について、次が基本的です:n=d|nφ(d)。つまり、ζ(s1)=Dφ(s)ζ(s) が成り立ちます。Dφ(s)=ζ(s1)ζ(s) であることが分かります。

なお、このことは、φ(n) の素因数分解による計算法を表した Euler 積 Dφ(s)=p(1+(p1)ps+(p2p)p2s+)=p1ps1pps からも確かめられます。

ζ(s)ζ(s1) に対して【命題 5】を用いることで、i=1Nφ(i)O(N2/3) 時間で計算できることが分かります。あるいは、Dφ(s)=ζ(s1)1ζ(s) から、Mertens 関数と【命題 4】を用いてもよいでしょう。

無平方数 (square-free number) の数え上げ

p2|n となる素数 p が存在しないときに、n無平方 (square-free) であるといいます。N 以下の無平方数を数え上げたいとしましょう。これは、Dirichlet 級数

f(s)=p(1+ps)

における ns の係数の累積和を求めよという問題です。1+ps=1p2s1ps より f(s)=ζ(s)ζ(2s) です。よって、f(s)ζ(2s)=ζ(s) なので、ζ(s)ζ(2s) を【命題 5】の形で管理できればよさそうです。

ζ(2s) は、平方数に対してのみ係数 1 を持つという単純な形をしているため、累積和も平方数の数え上げとして容易に行うことができます。よって、N 以下の無平方数の数え上げは O(N2/3) 時間で行うことができます。

疎な変形

少し難しい例も扱っておきましょう。例えば、a(n)=σ0(100n)σ0 は約数の個数の数え上げ関数)として、n=1Na(n) を求める問題を考えましょう。

σ0(n)a(n)n の素因数分解を元に計算できます。これは、Dirichlet 級数 Dσ0, Da が 「Euler 積分解」を持つことを示しています。例えば、

  • fp(s)=1+2ps+3p2s+=1(1X)2 (ただし X=ps)とおく
  • Dσ0(s)=pfp(s) が成り立つ

ことが分かります。npk 回割り切れるときに、k+1 をかけていく。という計算手順が、項 (k+1)pks に対応しています。なお、Dσ0(s)=ζ(s)2 でしたが、その表示と ζ(s)=p(1ps)1 を利用しても同じ結論に至ります。

Da はどうでしょうか?npk 回割り切れるときの計算ですが、p2,5 については σ0 と変わりません。したがって、DaDφ から疎な変形を行う形で表すことができます。

  • g2(s)=3+4X+5X2+6X3+ (ただし X=2s
  • g5(s)=3+4X+5X2+6X3+ (ただし X=5s

とすると、Da(n)=p2,5fp(s)g2(s)g5(s) と書けることが分かります。

Da(s)=Dσ0(s)g2(s)g5(s)f2(s)f5(s) となります。F(s)=g2(s)g5(s)f2(s)f5(s) はとても疎な Dirichlet 級数で、n=2a5b のところにしか 0 でない値を持ちません。

したがって、そのすべての項の計算や、累積和の計算は容易に行うことができ、 n=1Nσ0(100n)O(N2/3) 時間で計算できることが分かります。また、F が非常に疎であることを利用すると σ0 の累積和は n=N/(2a5b) の形の n についてしか行う必要がないことも分かり、全体として O(N) 時間で計算することもできます。

「疎な変形」としては、このように少数の素数部分の変形や、あるいはどの p でも 2 乗以上で割り切れる数(powerful number)部分の変形が代表的だと思います。

関連問題

Project Euler は、コンテンツの性質上さらなるヒントや解答コードの提示はできませんので、ご了承ください。

問題例:Multiple Sequences (ARC 116 [C])

本記事を執筆する直接的なきっかけとなった問題です。

C - Multiple Sequences
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

記号を適宜取り換えて、Ai を比に取り換えることで、次の問題であることが分かります。

自然数の組 (A1,,AM) であって、総積が n であるものの個数を fM(n) と書く。n=1NfM(n) を求めよ。

解法 1:O(N2/3logM) 時間解法

ζ(s)M=n=1fM(n)ns が成り立ちます。つまり、興味のある数列は、ζ を Dirichlet 積に関して M 乗したものです。

Dirichlet 級数について、必要な情報を【命題 4】の形で管理することにします。ζi は常に乗法的なので、Dirichlet 積の計算は 1 回あたり O(N2/3) 時間で行えます。

これを M 回計算するとひどい計算量になってしまいますが、Dirichlet 積は結合的なので繰り返し二乗法を適用でき、全体として O(N2/3logM) 時間で計算できます。

AtCoder コードテスト環境で、N=M=109 を 3 秒で計算することができています。

解法 2:O(N2/3logN) 時間解法

2 以上の 自然数の組 (A1,,Am) であって、総積が n であるものの個数をgm(n) と書くことにします。Gm=n=1Ngm(n) とすれば答は mGm(Mm) です。2m>NGm=0 であることはすぐに分かるので、0mlog2n に対して Gm を計算すればよいです。

ここで、n=1gm(n)ns=(ζ(s)1)m が成り立つことが分かります。「2 以上の自然数」というルールなので、n2ns=ζ(s)1 を用いています。

二項定理で展開することで、ζ(s)i (0ilog2n) に対する N 項累積和が計算できればよいと分かります。やはり、ζ1 回ずつ畳み込んでいけばよいです。

解答例:https://atcoder.jp/contests/arc116/submissions/21380945O(N2/3(loglogN)1/3) 時間計算量での実装)

N=M=109 に対して、約 1 秒の実行時間を実現できました。上の解法と比べて高速になりましたが、

  • 繰り返し二乗法の乗算回数 O(logN) 回よりも、この解法の乗算回数 O(logM) 回の方が、定数倍が小さい
  • こちらの解法は、乗算の対象が常に ζ(s) であるため、それを陽に配列として持つ必要がない

といった性質が挙げられます。

参考文献

タイトルとURLをコピーしました