文章沒有寫完,近期填完這坑
參考文章:
https://www.luogu.com.cn/blog/froggy/duo-xiang-shi-tai-za-hui
https://www.cnblogs.com/zwfymqz/p/8244902.html
https://www.cnblogs.com/RabbitHu/p/FFT.html
https://blog.csdn.net/enjoy_pascal/article/details/81478582
Thomas H.Cormen,Charles E.Leiserson,Ronald L.Rivest,Clifford Stein. 殷建平等譯. 算法導論第三版 [M]. 北京:機械工業出版社,2013,527-542.
多項式的定義:
一個 \(n\) 次多項式,是長成這樣的:
其中 \(a_i\) 是第 \(i\) 項的系數。
拉格朗日插值法:
給定 \(n\) 個點 \((x_i,y_i)\) 確定一個多項式 \(f(k)\),並求出 \(f(k)\) 的值。
定理:
不會證明這個。
代碼:
int n;
ll x[N], y[N], k;
ll ans;
ll qpow(ll a, ll b)
{
ll ans = 1;
for (; b; b >>= 1, a = a * a % mod)
if (b & 1) ans = ans * a % mod;
return ans;
}
int main()
{
scanf ("%d%lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf ("%lld%lld", &x[i], &y[i]);
for (int i = 1; i <= n; i++)
{
ll tmp = y[i];
for (int j = 1; j <= n; j++)
if(i != j)
tmp = tmp * ((k - x[j] + mod) % mod) % mod * qpow((x[i] - x[j] + mod) % mod, mod - 2) % mod;
ans = (ans + tmp) % mod;
}
printf ("%lld\n", ans);
return 0;
}
快速傅里葉變換 FFT:
為了方便計算,這里的 \(n\) 都是 \(2\) 的整次冪。
在此之前,先明白 DFT(離散傅里葉變換)、FFT(快速傅里葉變換)、IDFT(離散傅里葉逆變換)、IFFT(快速傅里葉逆變換) 之間的關系:
類似的,IDFT 也是概念,IFFT 是基於 IDFT 概念的算法。現在不知道這四個東西的具體含義在下文介紹。
給定兩個多項式 \(F(x),G(x)\),求出它們的卷積 \(F(x)*G(x)\)。
這里定義多項式卷積運算:
然后快速傅里葉變換可以在 \(\mathcal{O}(n\log n)\) 的時間復雜度內完成多項式乘法。
多項式的點值表示:
點值表示法,顧名思義就是把用 \(n\) 點的坐標表示一個多項式:
而點值表示法的優點就是可以在 \(\mathcal{O}(n)\) 的時間范圍內求出兩個多項式的乘積。
多項式的系數表示:
系數表示法,就是記錄 \(F(x)\) 每一項的系數,比如 \(F(x)=\sum_{i=0}^{n-1}a_ix^i\) 可以表示為:
那么我們可以推測,多項式乘法流程一般是:系數表示法先轉化到點值表示法,這樣就能快速求卷積,然后由轉回系數表示法。也就是有兩步,第一步系數轉點值就是 DFT;第二步點值轉系數 IDFT。
復數:
介紹:
提示:建議學習數學選修 2-2 的復數相關課程。
\(i\) 是虛數單位,規定:
- \(i^2=-1\);
- 實數可以與它進行四則運算,進行四則運算時,原有的加法和乘法運算律仍然成立。
有一個復數 \(z=a+bi(a,b\in\mathbb{R})\),其中 \(a,b\) 分別是 \(z\) 的實部與虛部。那么在復平面對應的平面直角坐標系中,\(z\) 在 \((a,b)\):
如圖點 \(Z\) 的復數是 \(z=3+4i\),它對應的平面直角坐標系中在 \((3,4)\)。
或者,你還可以把復數作成一個向量:
向量 \(\overrightarrow{OZ}\) 與復數 \(z=a+bi\) 一一對應。然后有:
共軛復數:
一般地,當兩個復數的實部相等,虛部互為相反數時,這兩個復數叫做互為共軛復數。通常記復數 \(z\) 的共軛復數為 \(\overline{z}\)。
比如當 \(z=a+bi\) 時,\(\overline{z}=a-bi\)。
復數的運算:
設 \(z_1=a+bi,z_2=c+di\) 是任意兩個復數,那么:
其中,復數相乘時,模長相乘,幅角相加。
DFT:
其實 DFT 的本質就是代入 \(x\) 得到點值,而這幾個 \(x\) 就是一些特殊的復數(選擇這些復數的原因不會證明/youl)。
單位根:
在復平面中,以原點為圓心,半徑為 \(1\) 的圓是 單位圓。然后將這個圓 \(n\) 等分,以圓心為起點,圓的 \(n\) 等分點為終點,做 \(n\) 個向量,設幅角為正且最小的向量對應的復數是 \(\omega_n\),也就是 \(n\) 次單位根。根據復數乘法性質,剩下 \(n-1\) 個復數是 \(\omega_n^2,\omega_n^3,\dots,\omega_n^n\)。其中,這幾個復數的值是:
這幾個復數就是那幾個 “特殊的復數”。
單位根的性質:
性質一:
證明:
性質二:
證明:
FFT 的流程:
朴素的 DFT 是 \(\mathcal{O}(n^2)\) 的,通過分治優化 DFT 得到 FFT。
設多項式 \(F(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1}\),然后按照奇偶性分為兩份:
設:
那么:
將 \(x=\omega_n^k\quad(k<\frac{n}{2})\) 代入得:
將 \(x=\omega_n^{k+\frac{n}{2}}\) 代入得:
然后我們就可以通過 \(\mathcal{O}(n\log n)\) 的時間復雜度遞歸了。
IFFT 的流程:
我們得到了點值表示 \(F(x)=\{(\omega_n^0,F(\omega_n^0)),(\omega_n^1,F(\omega_n^1)),\cdots,(\omega_n^{n-1},F(\omega_n^{n-1}))\}\)。設:\(y_k=\sum_{i=0}^{n-1}a_i\cdot\omega_n^{ki}\),我們要求系數:
關於證明,stoorz 爺的看法:
QuantAsk 爺的看法:
雖然二位爺都對我進行嘲諷,但這不影響我們證明。
將 \(y_i\) 的定義代入后面的和式得到:
發現最后的和式是等比數列求和。設 \(S(\omega_n^k)=\sum_{i=0}^{n-1}(\omega_n^k)^i\quad(k>0)\),用等比數列求和公式代入:
而 \(S(\omega_n^0)\) 更好求了:
代回原式:
\(a_i=\frac{1}{n}\sum_{j=0}^{n-1}y_j\cdot\omega_n^{-ij}\) 得證。
所以 IFFT 只用用對點值表示的 \(F(x)\) 像 FFT 一樣操作,只不過乘的是原來的復數的共軛,就可以求出系數表示的多項式了。
蝴蝶變換優化:
遞歸 FFT 常數大,所以嘗試把它變成迭代形式。
按奇偶性划分可以發現一個規律:
十進制 | 二進制 |
---|---|
\(0~1~2~3~4~5~6~7\) | \(000~001~010~011~100~101~110~111\) |
\(0~2~4~6,1~3~5~7\) | \(000~010~100~110,001~011~101~111\) |
\(0~4,2~6,1~5,3~7\) | \(000~100,010~110,001~101,011~111\) |
\(0,4,2,6,1,5,3,7\) | \(000,100,010,110,001,101,011,111\) |
每一個數最終的位置,就是把它的二進制翻轉過來。
所以每個數的位置就能夠 \(\mathcal{O}(n)\) 預處理出來(遞推式見代碼 tr[i]
)。
代碼:
const int N = 1500010;
const double PI = acos(-1.0);
struct Complex
{
double x, y;
Complex (double ix, double iy) {x = ix, y = iy;}
Complex () {x = y = 0;}
Complex operator + (Complex &b)
{
return Complex(x + b.x, y + b.y);
}
Complex operator - (Complex &b)
{
return Complex(x - b.x, y - b.y);
}
Complex operator * (Complex &b)
{
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
}f[N << 1], g[N << 1];
int n, m;
int tr[N << 1];
void FFT (Complex *f, bool isDFT)
{
for (int i = 1; i <= n; i++)
if (i < tr[i]) swap (f[i], f[tr[i]]);
for (int p = 2; p <= n; p <<= 1)
{
int len = p >> 1;
Complex omega(cos(2 * PI / p), sin(2 * PI / p));
if (!isDFT) omega.y *= -1; //共軛復數
for (int k = 0; k < n; k += p)
{
Complex tmp(1, 0);
for (int i = k; i < k + len; i ++)
{
Complex y = tmp * f[i + len];
f[i + len] = f[i] - y;
f[i] = f[i] + y;
tmp = tmp * omega;
}
}
}
if(!isDFT) for (int i = 0; i <= n; i++) f[i].x /= n;
}
int main()
{
scanf ("%d%d", &n, &m);
for (int i = 0; i <= n; i++)
scanf ("%lf", &f[i].x);
for (int i = 0; i <= m; i++)
scanf ("%lf", &g[i].x);
for (m += n, n = 1; n <= m; n <<= 1);
for (int i = 1; i <= n; i++)
tr[i] = (tr[i >> 1] >> 1) | (i & 1? n >> 1: 0);
FFT(f, 1), FFT(g, 1);
for (int i = 0; i <= n; i++) f[i] = f[i] * g[i];
FFT(f, 0);
for (int i = 0; i <= m; i++)
printf ("%d ", (int)(f[i].x + 0.49));
return 0;
}
快速數論變換 NTT:
由於 FFT 會有很大的精度誤差,所以用模數操作代替,就是 NTT 了,NTT 也比 FFT 要快很多。
原根的定義:
百度百科給的定義:
原根是一種數學符號,設 \(p\) 是正整數,\(g\) 是整數,若 \(g\) 模 \(p\) 的階等於 \(\varphi(p)\),則稱 \(g\) 為模 \(p\) 的一個原根。
簡單點說:
若整數 \(g\) 是奇素數 \(p\) 的一個原根,則滿足 \(g,g^2,g^3,\cdots,g^{p-1}\) 在模 \(p\) 意義下互不相同。
在模 \(998244353\) 意義下最小原根 \(g=3\)!
NTT 的基本流程:
若 \(p\) 滿足 \(2^k\cdot r+1\)(比如 \(998244353=2^{23}\times199+1\)),把 \(g^{\frac{p-1}{2^k}}\) 作為 \(\omega_n^1\),發現原本單位根的性質它都能滿足,那么就這么跑 FFT 可以了。
但 NTT 也有局限性,只能處理 \(n\leq 2^k\) 的情況,遇到模數 \(p=10^9+7\) 時,NTT 就做不了。所以后面有任意模數 NTT。
代碼:
const int N = 1500010;
const ll mod = 998244353ll, G = 3, invG = 332748118ll;
ll f[N << 1], g[N << 1];
int n, m;
int tr[N << 1];
ll qpow(ll a, ll b)
{
ll ans = 1;
for (; b; b >>= 1, a = a * a % mod)
ans = ans * (b & 1? a: 1) % mod;
return ans;
}
void NTT (ll *f, bool isDFT)
{
for (int i = 1; i <= n; i++)
if (i < tr[i]) swap (f[i], f[tr[i]]);
for (int p = 2; p <= n; p <<= 1)
{
int len = p >> 1;
ll omega = qpow(isDFT? G: invG, (mod - 1) / p);
for (int k = 0; k < n; k += p)
{
ll tmp = 1ll;
for (int i = k; i < k + len; i ++)
{
ll y = tmp * f[i + len] % mod;
f[i + len] = (f[i] - y + mod) % mod;
f[i] = (f[i] + y) % mod;
tmp = tmp * omega % mod;
}
}
}
if(!isDFT)
{
ll invn = qpow(n, mod - 2);
for (int i = 0; i <= n; i++) f[i] = f[i] * invn % mod;
}
}
int main()
{
scanf ("%d%d", &n, &m);
for (int i = 0; i <= n; i++)
scanf ("%lld", &f[i]);
for (int i = 0; i <= m; i++)
scanf ("%lld", &g[i]);
for (m += n, n = 1; n <= m; n <<= 1);
for (int i = 1; i <= n; i++)
tr[i] = (tr[i >> 1] >> 1) | (i & 1? n >> 1: 0);
NTT(f, 1), NTT(g, 1);
for (int i = 0; i <= n; i++) f[i] = f[i] * g[i];
NTT(f, 0);
for (int i = 0; i <= m; i++)
printf ("%lld ", f[i]);
return 0;
}
多項式乘法逆:
給定一個多項式 \(F(x)\) ,請求出一個多項式 \(G(x)\), 滿足 \(F(x) * G(x) \equiv 1\pmod{x^n}\)。系數對 \(998244353\) 取模。
思路:
運用倍增的思想求解。
設已經求出 \(G'(x)\equiv F(x)^{-1}\pmod{x^{\frac{n}{2}}}\),將 \(G(x)\equiv F(x)^{-1}\pmod{x^n}\) 與之相減得:
式子兩邊同時取平方:
式子兩邊同時乘 \(F(x)\):
接着就可以套 NTT 求解了。
代碼:
const int N = 1500010;
const ll mod = 998244353ll, G = 3, invG = 332748118ll;
ll f[N << 1], g[N << 1];
int n, m;
int tr[N << 1];
ll qpow(ll a, ll b)
{
ll ans = 1;
for (; b; b >>= 1, a = a * a % mod)
ans = ans * (b & 1? a: 1) % mod;
return ans;
}
void NTT (ll *f, bool isDFT, int n)
{
for (int i = 1; i <= n; i++)
if (i < tr[i]) swap (f[i], f[tr[i]]);
for (int p = 2; p <= n; p <<= 1)
{
int len = p >> 1;
ll omega = qpow(isDFT? G: invG, (mod - 1) / p);
for (int k = 0; k < n; k += p)
{
ll tmp = 1ll;
for (int i = k; i < k + len; i ++)
{
ll y = tmp * f[i + len] % mod;
f[i + len] = (f[i] - y + mod) % mod;
f[i] = (f[i] + y) % mod;
tmp = tmp * omega % mod;
}
}
}
if(!isDFT)
{
ll invn = qpow(n, mod - 2);
for (int i = 0; i <= n; i++) f[i] = f[i] * invn % mod;
}
}
ll h[N << 1];
void Inv(ll *f, ll *g, int m)
{
if (m == 1)
{
g[0] = qpow(f[0], mod - 2);
return ;
}
Inv(f, g, (m + 1) / 2);
int n;
for (n = 1; n < (m << 1); n <<= 1);
for (int i = 0; i <= n; i++)
tr[i] = (tr[i >> 1] >> 1) | (i & 1? n >> 1: 0),
h[i] = f[i] * (i <= m);
NTT(h, 1, n), NTT(g, 1, n);
for (int i = 0; i <= n; i++)
g[i] = (2 - g[i] * h[i] % mod + mod) % mod * g[i] % mod;
NTT(g, 0, n);
for (int i = m; i <= n; i++) g[i] = 0;
}
int main()
{
scanf ("%d", &n);
for (int i = 0; i < n; i++)
scanf ("%lld", &f[i]);
Inv(f, g, n);
for (int i = 0; i < n; i++)
printf ("%lld ", g[i]);
return 0;
}
多項式對數函數:
給定一個多項式 \(F(x)\) ,請求出一個多項式 \(G(x)\), 滿足 \(G(x) \equiv \ln F(x)\pmod{x^n}\)。系數對 \(998244353\) 取模。
求導:
提示:建議學習數學選修 2-2 的導數相關課程。
瞬時變化率:
先咕到這罷!