与离散傅里叶变换类似,Chirp Z 变换是给出多项式 和 求出 的一种算法,不要求 为单位根。也可用于数论变换。后文将介绍 Chirp Z 变换与其逆变换。
Chirp Z 变换
根据定义,Chirp Z 变换可以写作
其中 且 。
Bluestein 算法
考虑
其中 ,我们可以构造
其中 ,且对于 我们有
且 ,。可以由一次多项式乘法完成求算,该算法被称为 Bluestein 算法。
std::vector<uint> CZT(const std::vector<uint>& f, uint q, int n) { if (f.empty() || n == 0) return std::vector<uint>(n); const int m = f.size(); if (q == 0) { std::vector<uint> res(n, f[0]); for (int i = 1; i < m; ++i) if ((res[0] += f[i]) >= MOD) res[0] -= MOD; return res; } // H[i] = q^(binom(i + 1, 2)) std::vector<uint> H(std::max(m, n - 1)); H[0] = 1; // H[0] = q^0 uint qi = 1; // qi = q^i for (int i = 1; i < (int)H.size(); ++i) { qi = (ull)qi * q % MOD; // H[i] = q^(binom(i, 2)) * q^i H[i] = (ull)H[i - 1] * qi % MOD; } // F[i] = f[i] * q^(binom(i + 1, 2)) std::vector<uint> F(m); for (int i = 0; i < m; ++i) F[i] = (ull)f[i] * H[i] % MOD; std::vector<uint> G_p(m + n - 1); // G[i] = q^(-binom(i, 2)), -(m - 1) ≤ i < n uint* const G = G_p.data() + (m - 1); const uint iq = InvMod(q); // G[-(m - 1)] = q^(-binom(-(m - 1), 2)), // binom(-(m - 1), 2) = (-(m - 1)) * (-(m - 1) - 1) / 2 // = (m - 1) * m / 2 G[-(m - 1)] = PowMod(iq, (ull)(m - 1) * m / 2); uint qmi = PowMod(q, m - 1); // q^(-i), -(m - 1) ≤ i < n for (int i = -(m - 1) + 1; i < n; ++i) { // q^(-binom(i, 2)) = q^(-binom(i - 1, 2)) * q^(-(i - 1)) G[i] = (ull)G[i - 1] * qmi % MOD; // q^(-i) = q^(-(i - 1)) * q^(-1) qmi = (ull)qmi * iq % MOD; } // res[i] = q^(-binom(i, 2)) * f(q^i), 0 ≤ i < n std::vector<uint> res = MiddleProduct(G_p, F); for (int i = 1; i < n; ++i) res[i] = (ull)res[i] * H[i - 1] % MOD; return res; }
逆 Chirp Z 变换
逆 Chirp Z 变换可以写作
其中 且 ,并且 对于所有 成立,这是多项式插值的条件。
Bostan–Schost 算法
回顾 Lagrange 插值公式 为
且 对于所有 成立。与 多项式的快速插值 中相同,我们令 ,根据洛必达法则,有
修正 Lagrange 插值公式 就是
那么现在我们有
其中 。若我们设 为偶数,令 和 ,那么
这使得我们可以快速计算 。然后用 Bluestein 算法来计算 。令 ,我们有
因为 ,我们只需计算 ,其中 ,也就是
其中 。我们可以用 Bluestein 算法来计算 。
简单来说,我们分别进行下面的计算:
- 用减治法(decrease and conquer)计算 ;
- 用 Bluestein 算法计算 ;
- 用 Bluestein 算法计算 ;
- 用一次多项式乘法计算 。
其中每一步的时间复杂度都等于两个次数小于等于 的多项式相乘的时间复杂度。
模板实现
std::vector<uint> InvCZT(const std::vector<uint>& f, uint q) { if (f.empty()) return {}; const int n = f.size(); if (q == 0) { // f[0] = f(1), f[1] = f(0) assert(n <= 2); if (n == 1) return {f[0]}; // deg(f) < 1 return {f[1], (f[0] + MOD - f[1]) % MOD}; // deg(f) < 2 } // prod[0 ≤ i < n] (x - q^i) const auto DaC = [q](auto&& DaC, int n) -> std::vector<uint> { if (n == 1) return {MOD - 1, 1u}; // H = prod[0 ≤ i < ⌊n/2⌋] (x - q^i) const std::vector<uint> H = DaC(DaC, n / 2); // HH = H(x / q^(⌊n/2⌋)) * q^(⌊n/2⌋^2) std::vector<uint> HH = H; const uint iqn = InvMod(PowMod(q, n / 2)); uint qq = PowMod(q, (ull)(n / 2) * (n / 2)); for (int i = 0; i <= n / 2; ++i) HH[i] = (ull)HH[i] * qq % MOD, qq = (ull)qq * iqn % MOD; std::vector<uint> res = Product(H, HH); if (n & 1) { res.resize(n + 1); const uint qnm1 = MOD - PowMod(q, n - 1); for (int i = n; i > 0; --i) { if ((res[i] += res[i - 1]) >= MOD) res[i] -= MOD; res[i - 1] = (ull)res[i - 1] * qnm1 % MOD; } } return res; }; const std::vector<uint> M = DaC(DaC, n); // C[i] = (M'(q^i))^(-1) std::vector<uint> C = InvModBatch(CZT(Deriv(M), q, n)); // C[i] = f(q^i) / M'(q^i) for (int i = 0; i < n; ++i) C[i] = (ull)f[i] * C[i] % MOD; // C(x) ← C(q^(-1)x) const uint iq = InvMod(q); uint qmi = 1; for (int i = 0; i < n; ++i) C[i] = (ull)C[i] * qmi % MOD, qmi = (ull)qmi * iq % MOD; C = CZT(C, iq, n); for (int i = 0; i < n; ++i) if (C[i] != 0) C[i] = MOD - C[i]; std::vector<uint> res = Product(M, C); res.resize(n); return res; }