多項式大總結


文章沒有寫完,近期填完這坑

參考文章:

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\) 次多項式,是長成這樣的:

\[f(x)=\sum\limits_{i=0}^n{a_i x^i}\quad (a_n\neq 0) \]

其中 \(a_i\) 是第 \(i\) 項的系數。

拉格朗日插值法:

洛谷模板題

給定 \(n\) 個點 \((x_i,y_i)\) 確定一個多項式 \(f(k)\),並求出 \(f(k)\) 的值。

定理:

\[f(k)=\sum_{i=1}^{n}y_i\prod_{j\neq i}\frac{k-x_j}{x_i-x_j} \]

不會證明這個。

代碼:

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)\)

這里定義多項式卷積運算:

\[(F*G)(x)=\sum_{i+j=x}F_i\cdot G_{j} \]

然后快速傅里葉變換可以在 \(\mathcal{O}(n\log n)\) 的時間復雜度內完成多項式乘法。

多項式的點值表示:

點值表示法,顧名思義就是把用 \(n\) 點的坐標表示一個多項式:

\[F(x)=\{(x_0,F(x_0)),(x_1,F(x_1)),\cdots,(x_{n-1},F(x_{n-1}))\} \]

而點值表示法的優點就是可以在 \(\mathcal{O}(n)\) 的時間范圍內求出兩個多項式的乘積。

多項式的系數表示:

系數表示法,就是記錄 \(F(x)\) 每一項的系數,比如 \(F(x)=\sum_{i=0}^{n-1}a_ix^i\) 可以表示為:

\[F(x)=\{a_0,a_1,\cdots,a_{n-1}\} \]

那么我們可以推測,多項式乘法流程一般是:系數表示法先轉化到點值表示法,這樣就能快速求卷積,然后由轉回系數表示法。也就是有兩步,第一步系數轉點值就是 DFT;第二步點值轉系數 IDFT

復數:

介紹:

提示:建議學習數學選修 2-2 的復數相關課程。

\(i\) 是虛數單位,規定:

  1. \(i^2=-1\)
  2. 實數可以與它進行四則運算,進行四則運算時,原有的加法和乘法運算律仍然成立。

有一個復數 \(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\) 一一對應。然后有:

\[|\overrightarrow{OZ}|=|z|=|a+bi|=r=\sqrt{a^2+b^2}\quad(r\geq0,r\in\mathbb{R}) \]

共軛復數:

一般地,當兩個復數的實部相等,虛部互為相反數時,這兩個復數叫做互為共軛復數。通常記復數 \(z\) 的共軛復數為 \(\overline{z}\)

比如當 \(z=a+bi\) 時,\(\overline{z}=a-bi\)

復數的運算:

\(z_1=a+bi,z_2=c+di\) 是任意兩個復數,那么:

\[z_1+z_2=(a+c)+(b+d)i\\ z_1z_2=(ac-bd)+(ad+bc)i\]

其中,復數相乘時,模長相乘,幅角相加

DFT:

其實 DFT 的本質就是代入 \(x\) 得到點值,而這幾個 \(x\) 就是一些特殊的復數(選擇這些復數的原因不會證明/youl)。

單位根:

在復平面中,以原點為圓心,半徑為 \(1\) 的圓是 單位圓。然后將這個圓 \(n\) 等分,以圓心為起點,圓的 \(n\) 等分點為終點,做 \(n\) 個向量,設幅角為正且最小的向量對應的復數是 \(\omega_n\),也就是 \(n\) 次單位根。根據復數乘法性質,剩下 \(n-1\) 個復數是 \(\omega_n^2,\omega_n^3,\dots,\omega_n^n\)。其中,這幾個復數的值是:

\[\omega_n^k=\cos k\cdot\frac{2\pi}{n}+i\sin k\cdot\frac{2\pi}{n} \]

這幾個復數就是那幾個 “特殊的復數”。

單位根的性質:

性質一:

\[\omega_{n}^{k}=\omega_{2n}^{2k} \]

證明:

\[\omega_{n}^{k}=\cos k\cdot\frac{2\pi}{n}+i\sin k\cdot\frac{2\pi}{n}=\cos 2k\cdot\frac{2\pi}{2n}+i\sin 2k\cdot\frac{2\pi}{2n}=\omega_{2n}^{2k} \]

性質二:

\[\omega_{n}^{k + \frac{n}{2}} = -\omega_{n}^{k} \]

證明:

\[\begin{aligned}\omega_{n}^{\frac{n}{2}}&=\cos \frac{n\cdot2\pi}{2n}+i\sin\frac{n\cdot2\pi}{2n}\\&=\cos\pi+i\sin\pi\\&=-1\end{aligned} \]

FFT 的流程:

朴素的 DFT 是 \(\mathcal{O}(n^2)\) 的,通過分治優化 DFT 得到 FFT。

設多項式 \(F(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1}\),然后按照奇偶性分為兩份:

\[\begin{aligned}F(x)&=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+(a_1x+a_3x^3+\cdots+a_{n-1}x^{n-1})\\ &=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+x(a_1+a_3x^2+\cdots+a_{n-1}x^{n-2})\end{aligned}\]

設:

\[F_1(x)=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{\frac{n}{2}-1}\\ F_2(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac{n}{2}-1}\]

那么:

\[F(x)=F_1(x^2)+x\cdot F_2(x^2) \]

\(x=\omega_n^k\quad(k<\frac{n}{2})\) 代入得:

\[\begin{aligned}F(\omega_n^k)&=F_1(\omega_n^{2k})+\omega_n^k\cdot F_2(\omega_n^{2k})\\ &=F_1(\omega_{\frac{n}{2}}^{k})+\omega_n^k\cdot F_2(\omega_{\frac{n}{2}}^{k}) \end{aligned}\]

\(x=\omega_n^{k+\frac{n}{2}}\) 代入得:

\[\begin{aligned}F(\omega_n^{k+\frac{n}{2}})&=F_1(\omega_n^{2k+n})+\omega_n^{k+\frac{n}{2}}\cdot F_2(\omega_n^{2k+n})\\ &=F_1(\omega_n^{2k}\cdot\omega_n^n)+\omega_n^{k+\frac{n}{2}}\cdot F_2(\omega_n^{2k}\cdot\omega_n^n)\\ &=F_1(\omega_{\frac{n}{2}}^{k})-\omega_n^{k}\cdot F_2(\omega_{\frac{n}{2}}^{k})\\ \end{aligned}\]

然后我們就可以通過 \(\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}\),我們要求系數:

\[a_i=\frac{1}{n}\sum_{j=0}^{n-1}y_j\cdot\omega_n^{-ij} \]

關於證明,stoorz 爺的看法:

QuantAsk 爺的看法:

雖然二位爺都對我進行嘲諷,但這不影響我們證明。

\(y_i\) 的定義代入后面的和式得到:

\[\sum_{j=0}^{n-1}\sum_{k=0}^{n-1}a_k\cdot\omega_n^{kj}\cdot\omega_n^{-ij}=\sum_{j=0}^{n-1}\sum_{k=0}^{n-1}a_k\cdot\omega_n^{(k-i)j}=\sum_{k=0}^{n-1}a_k\cdot\sum_{j=0}^{n-1}\omega_n^{(k-i)j} \]

發現最后的和式是等比數列求和。設 \(S(\omega_n^k)=\sum_{i=0}^{n-1}(\omega_n^k)^i\quad(k>0)\),用等比數列求和公式代入:

\[\begin{aligned}S(\omega_n^k)&=\frac{(\omega_n^k)^n-1}{\omega_{n}^{k}-1}\\ &=\frac{(\omega_n^n)^k-1}{\omega_{n}^{k}-1}\\ &=\frac{1-1}{\omega_{n}^{k}-1}\end{aligned}\]

\(S(\omega_n^0)\) 更好求了:

\[S(\omega_n^0)=\sum_{i=0}^{n-1}(\omega_n^0)^i=\sum_{i=0}^{n-1}1=n \]

代回原式:

\[\sum_{k=0}^{n-1}a_k\cdot S(\omega_n^{k-i})=\sum_{k=0}^{n-1}a_k\cdot n[k=i]=a_i\cdot n \]

\(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}\) 與之相減得:

\[G(x)-G'(x)\equiv 0\pmod{x^{\frac{n}{2}}} \]

式子兩邊同時取平方:

\[G(x)^2-2\cdot G(x)\cdot G'(x)+G'(x)^2\equiv 0\pmod{x^n} \]

式子兩邊同時乘 \(F(x)\):

\[\begin{aligned}G(x)-2\cdot G'(x)+G'(x)^2\cdot F(x)&\equiv 0&\pmod{x^n}\\ G(x)&\equiv 2\cdot G'(x)-G'(x)^2\cdot F(x)&\pmod{x^n} \end{aligned}\]

接着就可以套 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 的導數相關課程。

瞬時變化率:

先咕到這罷!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM