離散フーリエ変換 (DFT) および数論変換 (NTT) の原理、そしてそれらのプログラミングにおける実装方法について記述します。
各用語の定義の違いを明確にするため、それぞれについての回を分割します。
前回までに考察した複素数体
そこで今回は、入力が整数の場合には整数の演算のみで済むよう、何らかの整数の集合上でも前回までの理論が成り立たないかを検討します。
この結果として、数論変換 (number-theoretic transform, NTT) が得られます。
実用性を考慮すると、結論としては素数
今後の議論に必要な定義および定理を準備します。定理の証明は省略します。
このときの
加法および乗法が定義されている何らかの整数の集合上で、これまでの DFT の議論で現れた2つの定理 (IDFT および畳み込み) が成り立つための十分条件を考察します。
まず、
すなわち、
このとき、
逆離散フーリエ変換 (IDFT) に関する定理が成り立つには、以前の証明の内容から、
(i)
(ii) 逆元
(iii) 逆元
が満たされていれば十分であることがわかります。
(i) については、
(ii) については、
(iii) については定理 1 により、剰余環
また、畳み込み定理については、加法および乗法が定義されていれば成り立ちます。
以上の考察から、次のようにまとめられます。
さらに、次が成り立つとする。
このとき、IDFT に関する定理および畳み込み定理は、値の集合と標本点の組
このような剰余環
ここまでの議論においては、
これらの強い制限は、次回の高速化で活用されることになります。
適用 | 備考 | |||||
---|---|---|---|---|---|---|
成立 | 可 | |||||
なし | 不可 | |||||
成立 | なし | 不可 | ||||
不可 | ||||||
成立 | 可 | |||||
成立 | 可 |
適用 | 備考 | |||||
---|---|---|---|---|---|---|
成立 | 可 | |||||
なし | 不可 | |||||
成立 | なし | 不可 | ||||
不可 | ||||||
成立 | 可 | |||||
成立 | 可 |
例えば
例えば
もし
であることは示せます。
そこで、
次回は、
DFT のコードを基に NTT を実装していきましょう。
今回はパラメーター
また、
using System;
public class NTT
{
// k 番目の 1 の n 乗根 (ω^k)
static long[] NthRoots(int n, long m, long w)
{
var r = new long[n];
r[0] = 1;
for (int k = 1; k < n; ++k)
r[k] = r[k - 1] * w % m;
return r;
}
int n;
long m, nInv;
long[] roots;
public NTT(int n, long m, long w, long nInv)
{
this.n = n;
this.m = m;
this.nInv = nInv;
roots = NthRoots(n, m, w);
}
// f(ω^k) の値
long f(long[] c, int k)
{
var r = 0L;
for (int j = 0; j < c.Length; ++j)
r = (r + c[j] * roots[k * j % n]) % m;
return r;
}
public long[] Transform(long[] c, bool inverse)
{
if (c == null) throw new ArgumentNullException(nameof(c));
var r = new long[n];
for (int k = 0; k < n; ++k)
r[k] = inverse ? f(c, n - k) * nInv % m : f(c, k);
return r;
}
public long[] Convolution(long[] a, long[] b)
{
if (a == null) throw new ArgumentNullException(nameof(a));
if (b == null) throw new ArgumentNullException(nameof(b));
var fa = Transform(a, false);
var fb = Transform(b, false);
for (int k = 0; k < n; ++k)
{
fa[k] = fa[k] * fb[k] % m;
}
return Transform(fa, true);
}
}
Transform, Convolution 関数のいずれも、時間計算量は
では Convolution 関数をテストしてみましょう。
using System;
static class Program
{
static void Main()
{
Test(5, 31, 2, 25);
Test(5, 121, 3, 97);
Test(5, 341, 4, 273);
Test(6, 31, 6, 26);
Test(6, 49, 19, 41);
Test(6, 91, 10, 76);
}
static void Test(int n, long m, long w, long nInv)
{
var a = new long[] { 2, 1, 1 };
var b = new long[] { m - 1, m - 1, 1 };
var ntt = new NTT(n, m, w, nInv);
// { m - 2, m - 3, 0, 0, 1 }
var c = ntt.Convolution(a, b);
}
}
この例は
前節で調べた、NTT が適用可能なパラメーターに対して実行しています。
前回:
(4) ビット反転置換
次回:
(6) NTT の高速化