任意模數NTT 拆係數FFT

2022-05-27 11:27:14 字數 3063 閱讀 1049

給定 \(2\) 個多項式 \(f(x), g(x)\) ,請求出 \(f(x) * g(x)\)。

係數對 \(p\) 取模,且不保證 \(p\) 可以分解成 \(p = a \cdot 2^k + 1\) 之形式。

不關心\(1 \leq n \leq 10^5, 0 \leq a_i, b_i \leq 10^9, 2 \leq p \leq 10^9 + 9\)

現在這裡有兩個多項式\(a(x),b(x)\),這兩個多項式我們都需要做一次dft

那麼我們定義\(p(x)=a(x)+i*b(x), q(x)=a(x)-i*b(x)\)

我們把\(p(x)\)經過dft後的第\(k\)項記為\(dft(p_k)\) (其他的也同理),經過一些證明我們可以得到這樣乙個結論:\(dft(p_k)和dft(q_)\)互為共軛複數,特殊的,\(dft(p_0)\)和\(dft(q_0)\)為共軛複數

所以我們一次正向fft之後求出所有的\(dft(p_k)\),就可以直接算出\(dft(q_k)\)

而此時\(dft(a_k)=\dfrac, dft(b_k)=-i*\dfrac\) 也是直接算出來就好了

綜上 我們只用了一次fft就對\(a(x)\)和\(b(x)\)都進行了dft 而不用兩次

**實現:

void dft(complex *a, complex *b) 

fft(p, 1);

for (int i = 0; i < lim; i++)

for (int i = 0; i < lim; i++)

}

idft也可以一次fft處理兩個多項式

對於兩個點值表達的多項式\(c(x), d(x)\)

我們讓\(r(x)=c(x)+i*d(x)\)

然後對\(r\)進行反向fft

最後\(idft(r_k)\)的實部就是\(idft(c_k)\),\(idft(r_k)\)的虛部就是\(idft(d_k)\)

這個就更好實現了

void idft(complex *c, complex *d) 

fft(r, -1);

for (int i = 0; i < lim; i++)

}

所以我們又只用了一次fft就算出兩個點值表示式idft後的結果

好像有人給這個取了個名字叫mtt

這個技巧的使用沒有什麼限制,只要有兩個多項式都需要進行dft/idft就可以用

不卡時限的題目還是不怎麼需要用到這個技巧的。。。但是此題就要用

回到此題

當然你可以用三模數ntt 但是蒟蒻我並不會用

所以用fft

如果直接fft爆乘的話 肯定會爆出double的範圍

但是 如果我們把f(x)拆成兩個多項式\(f(x)=a(x)*2^+a_2(x)\),把\(g(x)\)拆成\(g(x)=b(x)*2^+b_2(x)\)

然後計算\((a(x)*2^+a_2(x))(b(x)*2^+b_2(x))=a(x)*b(x)*2^+(a(x)*b_2(x)+a_2(x)*b(x))*2^+a_2(x)*b_2(x)\)

這四個式子兩兩相乘是不會乘爆的

但是這樣做需要做8次fft(為了不乘爆最少也要7次) 時間上接受不了

用上面的那個技巧 我們可以把fft的次數優化到4次

具體來說 就是\(a(x)和a_2(x)\)的dft一起做 \(b(x)和b_2(x)\)的dft一起做

然後分別算出\(a(x)*b(x),a(x)*b_2(x),a_2(x)*b(x),a_2(x)*b_2(x)\),同樣兩個一組做idft

一共4次 完美 時間上還過得去 只比ntt慢一倍左右吧

注意一定要用long double。。。可能是乘積太大了導致精度不夠用

#include using namespace std;

typedef long long ll;

templateinline void read(t &num)

struct complex

};inline complex operator + (complex p, complex q)

inline complex operator - (complex p, complex q)

inline complex operator * (complex p, complex q)

inline complex operator / (complex p, long double q)

inline complex conj(complex p)

complex i = complex(0, 1), p[500005], q[500005], a[500005], a2[500005], b[500005], b2[500005];

ll n, m, mod, lim, l, rev[500005], ans[500005];

const long double pi = acos(-1.0);

void fft(complex *c, int tp)

for (int mid = 1; mid < lim; mid <<= 1)

} }}void dft(complex *a, complex *b)

fft(p, 1);

for (int i = 0; i < lim; i++)

for (int i = 0; i < lim; i++)

} int main()

for (int i = 0; i < lim; i++)

for (int i = 0; i <= n; i++)

for (int i = 0; i <= m; i++)

dft(a, a2); dft(b, b2);

for (int i = 0; i < lim; i++)

fft(p, -1); fft(q, -1);

for (int i = 0; i < lim; i++)

for (int i = 0; i <= n + m; i++)

return 0;

}

任意模數ntt 任意模數NTT

任意模數 ntt 眾所周知,為了滿足單位根的性質,ntt 需要質數模數,而且需要能寫成 a2 1 且 2 k ge n 比較常用的有 998244353,1004535809,469762049 這三個原根都是 3 如果要任意模數怎麼辦?n 次多項式在模 m 下乘積,最終係數一定不會大於 nm 2 ...

任意模數ntt 模板篇 NTT和三模數NTT

之前寫過fft的筆記.我們知道fft是在複數域上進行的變換.而且經過數學家的證明,dft是複數域上唯一滿足迴圈卷積性質的變換.而我們在oi中,經常遇到對 x取模的題目,這就啟發我們可不可以在模運算的意義下找乙個這樣的變換.然後我們發現有個神奇的東西,原根 g 這東西在模意義下相當於單位復根 e 所以...

任意模數NTT學習筆記

這兩天有點頹,所以東西學的也很慢。這個一眼就能推出來的活生生卡了我兩天。說幾個細節 柿子 m 通常設定為 32768 把上一步的幾個韓束化成 a,b,c,d 的形式,答案就是 一看卷積,多搞幾次 fft 就過去了。陣列記得開大。n 2 左右。include using namespace std d...