Sep 1, 2018 由 小羊
要计算的式子一般有这样的特性:自己的后项由前项推出。时间
// dp[i] = sigma(a[j] * dp[i-j]) (j < i);
num_t x[MAXN], y[MAXN], a[MAXN], dp[MAXN];
void cdqFFT(int L, int R)
{
if (L == R) return; // (assert that a[0] == 0)
int mid = (L + R) >> 1;
cdqFFT(L, mid);
int len = R - L + 1;
trans.init(log2_ceil2(len));
// x = a0, a1, a2, ..., a[len], 0, 0, ...
// y = dp[L], dp[L+1], dp[L+2], ..., dp[mid], 0, 0, ...
for (int i = 0; i < trans.N; i++)
{
x[i] = i < len ? a[i] : 0;
y[i] = (i <= mid-L) ? dp[i+L] : 0;
}
trans.DFT(x); trans.DFT(y);
for (int i = 0; i < trans.N; ++i)
x[i] = x[i] * y[i];
trans.IDFT(x);
for (int i = mid + 1; i <= R; ++i)
dp[i] += x[i-L];
cdqFFT(mid + 1, R);
}
num_t tp[MAXN], tp2[MAXN], tp3[MAXN], A[MAXN];
inline void cdqFFT(int l, int r)
{
if (l == r)
{
if (l == 0)
{
A[l] = 1;
}
else
{
A[l] = (l % 3 == 1) ? (A[l] + A[l/3] * 2) % mod : A[l]; // for the last item.
A[l] = A[l] * inv6 % mod;
}
return; // Last modify of A[i], so we can divide 6 here.
}
int mid = (l + r) >> 1, len = r - l;
cdqFFT(l, mid);
trans.init(log2_ceil2(len * 2 + mid - l));
// tp[] = A[l], A[l+1], A[l+2], ..., A[mid], 0, 0, ...
// tp2[] = A[0], A[1], A[2], ..., A[len-1], 0, 0, ...
// tp3[] = A[0], 0, A[1], 0, A[2], 0, ..., at most A[len/2].
for (int i = 0; i < trans.N; i++)
{
tp[i] = (i <= mid - l) ? A[i + l] : 0;
tp2[i] = (i < len) ? A[i] : 0;
tp3[i] = ((i & 1) == 0 && i <= len) ? A[i / 2] : 0;
}
trans.DFT(tp); trans.DFT(tp2); trans.DFT(tp3);
for (int i = 0; i < trans.N; i++)
tp2[i] = (tp2[i] * tp2[i] % mod) * tp[i] % mod;
trans.IDFT(tp2); // tp2-new = A[i]A[j]A[k], i+j+k=n
if (!l)
{
for (int i = mid + 1; i <= r; i++)
A[i] = (A[i] + tp2[i - l - 1]) % mod;
}
else
{
for (int i = mid + 1; i <= r; i++)
A[i] = (A[i] + tp2[i - l - 1] * 3) % mod;
}
for (int i = 0; i < trans.N; i++)
tp[i] = tp[i] * tp3[i] % mod; // tp-new = A[i]A[j], i+2j=n
trans.IDFT(tp);
for (int i = mid + 1; i <= r; i++)
A[i] = (A[i] + tp[i - l - 1] * 3) % mod;
cdqFFT(mid + 1, r);
}
可以将L~R内的所有项数值更新完成。
在 后,左边一半处理完成,然后利用左边一半的数值更新右边一半。
以上述第一个卷积为例,卷积完后,有
然后向后项更新时,会有
此时, 已经内含了 ,只需要计算
以上述第二个卷积为例,
此时, 已经内含了 ,此时被卷积的 ,而 ,没有利用到未计算完成的值。
在进行 时, 会被更新到新的 值,直到最后一次被更新。
时间复杂度 。
void diff(int a[], OUT int b[], int len)
{
for (int i = 1; i < len; i++)
b[i-1] = 1LL * i * a[i] % mod;
b[len] = b[len-1] = 0;
}
void integral(int a[], OUT int b[], int len)
{
for (int i = 1; i < len; i++)
b[i] = 1LL * a[i-1] * inv[i] % mod;
b[0] = 0;
}
当常数项与模互质时有逆元。
num_t A[_], B[_];
void get_inv(int a[], OUT b[], int len)
{
if (len == 1)
{
b[0] = fpow(a[0], mod-2);
return;
}
get_inv(a, b, len>>1);
for (int i = 0; i < len; i++)
A[i] = a[i], B[i] = b[i];
trans.init(log2_floor(len)+1);
trans.DFT(A); trans.DFT(B);
for (int i = 0; i < trans.N; i++)
A[i] = 1LL * A[i] * B[i] % mod * B[i] % mod;
trans.IDFT(A);
for (int i = 0; i < len; i++)
b[i] = (2LL * b[i] % mod - A[i] + mod) % mod;
for (int i = 0; i < trans.N; i++)
A[i] = B[i] = 0;
}
num_t C[_], D[_], inv2 = (mod-1)/2;
void get_sqrt(int a[], OUT int b[], int len)
{
if (len == 1)
{
b[0] = sqrt(a[0]);
return;
}
get_sqrt(a, b, len>>1);
for (int i = 0; i < len; i++)
C[i] = a[i];
get_inv(b, D, len);
trans.init(log2_floor(len)+1);
trans.DFT(C); trans.DFT(D);
for (int i = 0; i < trans.N; i++)
D[i] = 1LL * D[i] * C[i] % mod;
trans.IDFT(D);
for (int i = 0; i < len; i++)
b[i] = 1LL * (b[i] + D[i]) % mod * inv2 % mod;
for (int i = 0; i < trans.N; i++)
C[i] = D[i] = 0;
}
为了防止 爆long long,所以设一个常数
于是卷积就变成了这样的七次: