[算法学习] 多项式全家桶 - 2
多项式求逆
题目链接:洛谷P4238 多项式乘法逆
Description
给定一个多项式 \(F(x)\) ,请求出一个多项式 \(G(x)\) ,满足 \(F(x)*G(x) \equiv 1 (mod\ x^n)\)。系数对 \(998244353\) 取模。
数据范围 \(1\le n\le 10^5, 0\le a_i \le 10^9\)
时间限制 \(1\ s\)
Solution
求模 \(x^n\) 的逆元为 \(B\),模 \(x^{\frac{n}{2}}\) 逆元为 \(C\) , 则
\(\begin{cases} A*C\equiv 1 (mod\ x^{\frac{n}{2}}) \\ A*B\equiv 1 (mod\ x^{\frac{n}{2}}) \end{cases}\)
则\(C-B\equiv 0 (mod\ x^{\frac{n}{2}})\)。
两边平方,则\((C-B)^2 \equiv 0 (mod\ x^n)\)
\(C^2 - 2CB + B^2 \equiv 0 (mod\ x^n)\)
\(AC^2-2C+B \equiv 0 (mod\ x^n)\)
\(B \equiv 2C-AC^2 (mod\ x^n)\)
于是,我们可以递归求解,在求出\(C\)后,可将其转移到\(B\)上。
复杂度\(T(n)=T(\frac{n}{2})+nlogn\),由主定理可知复杂度为\(O(nlogn)\)。
Code
const int N = 400005;
const int mod = 998244353;
const int G = 3;
const int Gi = 332748118;
int qpow(int a, int b) {
int res = 1;
while (b > 0) {
if (b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int a[N], b[N], c[N], n;
namespace Poly {
int r[N], lim = 1, L = 0;
void getR(int n) {
lim = 1, L = 0;
while (lim <= n) lim <<= 1, L++;
for (rint i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << L - 1);
}
void NTT(int *a, int opt) {
for (rint i = 0; i < lim; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (rint mid = 1; mid < lim; mid <<= 1) {
int Wn = qpow(opt == 1 ? G : Gi, (mod - 1) / (mid << 1));
for (rint R = mid << 1, j = 0; j < lim; j += R) {
int w = 1;
for (rint k = 0; k < mid; k++, w = 1ll * w * Wn % mod) {
int x = a[j + k], y = 1ll * w * a[j + k + mid] % mod;
a[j + k] = (x + y) % mod;
a[j + k + mid] = (x - y + mod) % mod;
}
}
}
if (opt == -1) {
int linv = qpow(lim, mod - 2);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * linv % mod;
}
}
void mul(int *a, int *b, int n, int m) {
getR(n + m);
NTT(a, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * b[i] % mod;
NTT(a, -1);
}
void inv(int *a, int *b, int n) {
if (n == 1) { b[0] = qpow(a[0], mod - 2); return ; }
inv(a, b, (n + 1) >> 1);
getR(n + n);
for (rint i = 0; i < n; i++) c[i] = a[i];
for (rint i = n; i < lim; i++) c[i] = 0;
NTT(c, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) {
b[i] = (2ll - 1ll * c[i] * b[i] % mod + mod) % mod * b[i] % mod;
}
NTT(b, -1);
for (rint i = n; i < lim; i++) b[i] = 0;
}
}
int main() {
n = read();
for (rint i = 0; i < n; i++) a[i] = read();
Poly::inv(a, b, n);
for (rint i = 0; i < n; i++) print(b[i]), putchar(' ');
return 0;
}
多项式开根
题目链接:洛谷P5205 多项式开根
前置技能
多项式求逆
Description
给定一个多项式 \(A(x)\) ,求多项式 \(B(x)\) 使得 \(B^2(x)\equiv A(x) (mod\ x^n)\)。
若有多解,取零次项系数较小的作为答案,数据保证\(a_0=1\)。
Solution
求模 \(x^n\) 的逆元为 \(B\),模 \(x^{\frac{n}{2}}\) 逆元为 \(C\) , 则
\(\begin{cases} C^2\equiv A (mod\ x^{\frac{n}{2}}) \\ B^2\equiv A (mod\ x^{\frac{n}{2}}) \end{cases}\)
则\((B+C)(B-C)\equiv 0 (mod\ x^{\frac{n}{2}})\)
有两组解,我们取\(B-C\)这组。
\(B-C\equiv 0 (mod\ x^{\frac{n}{2}})\)
\(A-2BC+C^2\equiv 0 (mod\ x^n)\)
\(B\equiv \frac{A+C^2}{2C} (mod\ x^n)\)
递归求解即可,注意边界\(a_0=1\),所以在\(n=1\)时\(b_0=1\)即可。
复杂度 \(O(nlogn)\)
Code
const int N = 400005;
const int mod = 998244353;
const int G = 3;
const int Gi = 332748118;
int qpow(int a, int b) {
int res = 1;
while (b > 0) {
if (b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int a[N], b[N], c[N], d[N], f[N], n;
namespace Poly {
int r[N], lim = 1, L = 0;
void getR(int n) {
lim = 1, L = 0;
while (lim <= n) lim <<= 1, L++;
for (rint i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << L - 1);
}
void NTT(int *a, int opt) {
for (rint i = 0; i < lim; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (rint mid = 1; mid < lim; mid <<= 1) {
int Wn = qpow(opt == 1 ? G : Gi, (mod - 1) / (mid << 1));
for (rint R = mid << 1, j = 0; j < lim; j += R) {
int w = 1;
for (rint k = 0; k < mid; k++, w = 1ll * w * Wn % mod) {
int x = a[j + k], y = 1ll * w * a[j + k + mid] % mod;
a[j + k] = (x + y) % mod;
a[j + k + mid] = (x - y + mod) % mod;
}
}
}
if (opt == -1) {
int linv = qpow(lim, mod - 2);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * linv % mod;
}
}
void mul(int *a, int *b, int n, int m) {
getR(n + m);
NTT(a, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * b[i] % mod;
NTT(a, -1);
}
void inv(int *a, int *b, int n) {
if (n == 1) { b[0] = qpow(a[0], mod - 2); return ; }
inv(a, b, (n + 1) >> 1);
getR(n + n);
for (rint i = 0; i < n; i++) c[i] = a[i];
for (rint i = n; i < lim; i++) c[i] = 0;
NTT(c, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) {
b[i] = (2ll - 1ll * c[i] * b[i] % mod + mod) % mod * b[i] % mod;
}
NTT(b, -1);
for (rint i = n; i < lim; i++) b[i] = 0;
}
void Sqrt(int *a, int *b, int n) {
if (n == 1) { b[0] = sqrt(a[0]); return ; }
Sqrt(a, b, (n + 1) >> 1);
getR(n + n);
for (rint i = 0; i < n; i++) d[i] = 1ll * 2 * b[i] % mod;
for (rint i = 0; i < lim; i++) f[i] = 0;
inv(d, f, n);
NTT(b, 1);
for (rint i = 0; i < lim; i++) b[i] = 1ll * b[i] * b[i] % mod;
NTT(b, -1);
for (rint i = 0; i < n; i++) b[i] = (b[i] + a[i]) % mod;
NTT(b, 1), NTT(f, 1);
for (rint i = 0; i < lim; i++) b[i] = 1ll * b[i] * f[i] % mod;
NTT(b, -1);
for (rint i = n; i < lim; i++) b[i] = 0;
}
}
int main() {
n = read();
for (rint i = 0; i < n; i++) a[i] = read();
Poly::Sqrt(a, b, n);
for (rint i = 0; i < n; i++) print(b[i]), putchar(' ');
return 0;
}
多项式对数函数(ln)
前置技能
多项式求逆,求导,积分
Description
给定一个多项式 \(A(x)\) ,求多项式 \(B(x) \equiv ln(A(x)) (mod\ x^n)\)。
在模数为\(998244353\)下进行,保证 \(a_0=1\),且 \(a_i \in [0, 998244353) ∩ Z\)。
数据范围 \(1\le n\le 10^5\)
Solution
\(B \equiv ln(A) (mod\ x^n)\)
两边同时求导:\(B' \equiv \frac{A'}{A} (mod\ x^n)\)
那么,我们可以先计算出\(A'\),然后将\(A\)求逆,跑一次卷积,然后将\(B'\)积分回去即可。
复杂度 \(O(nlogn)\)
Code
const int N = 400005;
const int mod = 998244353;
const int G = 3;
const int Gi = 332748118;
int qpow(int a, int b) {
int res = 1;
while (b > 0) {
if (b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int a[N], b[N], c[N], d[N], aa[N], n;
namespace Poly {
int r[N], lim = 1, L = 0;
void getR(int n) {
lim = 1, L = 0;
while (lim <= n) lim <<= 1, L++;
for (rint i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << L - 1);
}
void NTT(int *a, int opt) {
for (rint i = 0; i < lim; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (rint mid = 1; mid < lim; mid <<= 1) {
int Wn = qpow(opt == 1 ? G : Gi, (mod - 1) / (mid << 1));
for (rint R = mid << 1, j = 0; j < lim; j += R) {
int w = 1;
for (rint k = 0; k < mid; k++, w = 1ll * w * Wn % mod) {
int x = a[j + k], y = 1ll * w * a[j + k + mid] % mod;
a[j + k] = (x + y) % mod;
a[j + k + mid] = (x - y + mod) % mod;
}
}
}
if (opt == -1) {
int linv = qpow(lim, mod - 2);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * linv % mod;
}
}
void mul(int *a, int *b, int n, int m) {
getR(n + m);
NTT(a, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) a[i] = 1ll * a[i] * b[i] % mod;
NTT(a, -1);
}
void inv(int *a, int *b, int n) {
if (n == 1) { b[0] = qpow(a[0], mod - 2); return ; }
inv(a, b, (n + 1) >> 1);
getR(n + n);
for (rint i = 0; i < n; i++) c[i] = a[i];
for (rint i = n; i < lim; i++) c[i] = 0;
NTT(c, 1), NTT(b, 1);
for (rint i = 0; i < lim; i++) {
b[i] = (2ll - 1ll * c[i] * b[i] % mod + mod) % mod * b[i] % mod;
}
NTT(b, -1);
for (rint i = n; i < lim; i++) b[i] = 0;
}
void Dao(int *a, int n) {
for (rint i = 0; i < n - 1; i++) {
a[i] = 1ll * a[i + 1] * (i + 1) % mod;
}
a[n - 1] = 0;
}
void Jifen(int *a, int n) {
for (rint i = n - 1; i > 0; i--) {
a[i] = 1ll * a[i - 1] * qpow(i, mod - 2) % mod;
}
a[0] = 0;
}
void Ln(int *a, int *b, int n) {
for (rint i = 0; i < n; i++) aa[i] = a[i];
Dao(aa, n), inv(a, d, n);
mul(aa, d, n, n);
Jifen(aa, n);
for (rint i = 0; i < n; i++) b[i] = aa[i];
}
}
int main() {
n = read();
for (rint i = 0; i < n; i++) a[i] = read();
Poly::Ln(a, b, n);
for (rint i = 0; i < n; i++) print(b[i]), putchar(' ');
return 0;
}
多项式指数函数(exp)
Description
给定一个多项式 \(A(x)\) ,求一个多项式 \(B(x)\) 满足 \(B(x) \equiv e^{A(x)} (mod\ x^n)\)。
系数对 \(998244353\) 取模。
数据范围 \(1\le n\le 100000\),保证 \(a_0=1\)。
Solution
rt

浙公网安备 33010602011771号