Polynomial Taylor Shift を履修したので、簡単にまとめてみます。例によって、Yosupo Library Checker の 問題 を通します。
なお、Polynomial Taylor Shift の解法は、単なるライブラリ整備の一環とみなすというよりは、その導出過程をぜひ押さえておきたい問題だと思いました。他の問題でも使える典型的な考え方がたくさん登場します!
目次
- 1. Polynomial Taylor Shift とは
- 2. 前提知見
- 3. Polynomial Taylor Shift の解法
- 4. 実装例
- 5. 問題例 (ABC 215 G - Colorful Candies 2
1. Polynomial Taylor Shift とは
Polynomial Taylor Shift とは、整数係数の多項式
が与えられたときに、整数定数 に対して を展開したときの各係数を求めたいという問題です。ここでは、mod 998244353 で求めます。
たとえば、
のとき、
となります。
愚直に計算すると 個の項を整理することになりますので、 の計算量が必要になります。しかし実は、畳み込み計算一発で求めることができて、 の計算量で求められます。
2. 前提知見
Polynomial Taylor Shift の解法に登場する知見を 2 つ紹介します。これらの知見は、Polynomial Taylor Shift 以外のさまざまな問題でも使える典型的・汎用的なものです!
知見その 1:添字の差が一定の積和 → 畳み込み
畳み込みというと、添字の和が一定の積和を計算するイメージが強いです。具体的には、2 つの数列 、 に対して、
によって定まる数列 を の計算量で高速に計算することができます。多項式で表すならば、
とすると次のように表せます。
一方、実は、添字の差が一定の積和を計算する際にも、高速畳み込みアルゴリズムを活用できます。具体的には、
を畳み込み計算に帰着してみましょう。2 つの多項式 を次のように定義します。 の係数が先程とは逆になっています。
このとき、 を係数にもつ項の次数は 、 を係数にもつ項の次数は なので、
となります。つまり、多項式の積 を NTT などで計算することによって、 の値を計算量 で求められます。
知見その 2:シグマの入れ替え
式変形をしていると、次の形の二重シグマにしばしば出会います。
これは次のようにして、添字の順序を入れ替えることができます。
これは一体どうなっているのでしょうか。一般に、二重シグマ計算で添字を入れ替えたいとき、添字の組 の表す領域を二次元平面上に図示することが有効です。 のとき、下図のようになります。
上の式変形は、左側の式は下図の左側のように縦方向に見ていて、右側の式は下図の右側のように横方向に見ていると解釈できます。
このような式変形は、競プロをしていると、さまざまな場面で登場します!
3. Polynomial Taylor Shift の解法
これらの前提知見を踏まえて、Polynomial Taylor Shift を畳み込みへと帰着します。 に対して、 を二項定理を用いて計算していきましょう。
ここで、「知見その 2:シグマの入れ替え」を用いて、
となります。ここで、最後の式変形は、添字 が 0 始まりとなるように調整しました。これより、 の各係数 は、次のように表せることがわかりました。
さて、このシグマ計算の部分は、まさに「知見その 1:添字の差が一定の積和 → 畳み込み」を適用できる形となっています。よって、全体として の計算量で の各係数値が求められることが分かりました。
4. 実装例
ここでは、Yosupo Library Checker の Polynomial Taylor Shift を通るコードを書きます。ACL の convolution
を活用しますた。
#include <bits/stdc++.h> #include "atcoder/convolution.hpp" #include "atcoder/modint.hpp" using namespace std; using namespace atcoder; using mint = modint998244353; // Binomial coefficient const int MOD = 998244353; template<class T> struct BiCoef { vector<T> fact_, inv_, finv_; constexpr BiCoef() {} constexpr BiCoef(int n) noexcept : fact_(n, 1), inv_(n, 1), finv_(n, 1) { init(n); } constexpr void init(int n) noexcept { fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1); for(int i = 2; i < n; i++){ fact_[i] = fact_[i-1] * i; inv_[i] = -inv_[MOD%i] * (MOD/i); finv_[i] = finv_[i-1] * inv_[i]; } } constexpr T com(int n, int k) const noexcept { if (n < k || n < 0 || k < 0) return 0; return fact_[n] * finv_[k] * finv_[n-k]; } constexpr T fact(int n) const noexcept { if (n < 0) return 0; return fact_[n]; } constexpr T inv(int n) const noexcept { if (n < 0) return 0; return inv_[n]; } constexpr T finv(int n) const noexcept { if (n < 0) return 0; return finv_[n]; } }; // Polynomial Taylor Shift // given: f(x), c // find: coefficientss of f(x + c) template<class mint> vector<mint> PolynomialTaylorShift(const vector<mint> &f, long long c) { int N = (int)f.size() - 1; BiCoef<mint> bc(N + 1); // convollution vector<mint> p(N + 1), q(N + 1); for (int i = 0; i <= N; ++i) { p[i] = f[i] * bc.fact(i); q[N - i] = mint(c).pow(i) * bc.finv(i); } vector<mint> pq = convolution(p, q); // find vector<mint> res(N + 1); for (int i = 0; i <= N; ++i) res[i] = pq[i + N] * bc.finv(i); return res; } int main() { long long N, c; cin >> N >> c; vector<mint> a(N); for (int i = 0; i < N; ++i) { long long v; cin >> v; a[i] = v; } vector<mint> res = PolynomialTaylorShift(a, c); for (int i = 0; i < N; ++i) cout << res[i].val() << " "; cout << endl; }
5. Polynomial Taylor Shift が使える問題例
ABC 215 G - Colorful Candies 2 は、Polynomial Taylor Shift を使うことで、 の計算量で解ける問題でした。
この問題は結局、総和が である数列 が与えられて、 に対して
を求める問題へと帰着されます。これは、
と変形できます。よって、
として、 の各係数を求める問題と捉えることができて、Polynomial Taylor Shift で解くことができます。