寫法:
vector寫有什麼好處?
1.分治NTT的時候不用處理麻煩的下標。
2.傳進函數的時候不用傳指針而不會爆棧。
3.copy,swap,reverse,clear,resize等vector的函數可以幫助您快速地處理。
vector寫的時候注意什麼?
由於本人非常非常地懶,所以全部都是用vector寫的。
根據測試,在開了 O2優化的情況下,是沒有什麼區別的。
但是不開O2,你懂的……
還是想用vector怎麼辦呢?
又經過一輪測試,只要在dft的時候把vector裏的東西copy到一個數組,用這個數組做,再copy回去,是差不多的。
Dft:
不說了太累了。
我們設一次dft的常數爲1,以便後面分析複雜度。
求逆:
設對多項式A求逆。
倍增法。
設有兩個多項式
其實可以用去推,推導如下:
首先B0的初值即n=1時
等式兩邊同時平方:
按A整理:
所以
我們來思考一下怎麼做會快一點:
對B0求dft,對A求dft,然後算一算,再IDFT回去。
那麼需要三次,但是每層的次數界必須開到4*n
複雜度爲
因爲
所以複雜度大概爲6次dft
開二次根:
方法類似於求逆,也是用倍增的思想。
已知,推
B0的初值可能需要二次剩餘,一般多項式開根的題的常數項是個常數,可以直接預處理。
每層做的時候注意次數界是2n
每層要兩次和求逆一次,複雜度大概是2*(3+6)=18次dft?
取模:
給出
求滿足的
設
我們來理一下次數的關係:
對等式兩邊同時,則沒了。
設就是把A反過來。
那麼:
坑點:必須要先再倒置。
求C要一次求逆和一次NTT,需要6+3=9次dft。
求了C後,,需要一次NTT,即3次dft。
對數:
前置技能:
1.複合函數求導:
2.ln函數求導:
只需要求逆一次和NTT一次,9次dft
指數:
前置技能:
牛頓迭代:
https://blog.csdn.net/ccnt_2012/article/details/81837154
可以發現這就是個求函數零點的玩意兒,雖然有些侷限性,但是面對exp函數這麼優美的圖像肯定是沒有問題的。
現在來思考多項式牛頓迭代,即求,其中是一個多項式。
思路是倍增,即知道了前項的答案,去推前項的答案。
我們已知前項爲多項式B0,想要求出前項B。
可以直接由牛頓迭代得出:
下面介紹一下用泰勒展開推的方法:
注意當i>1時,每一項最低次項都是
即:
會了牛頓迭代之後思考如何把換出牛頓迭代的形式:
設
等式兩邊同時取,得
那麼,現在就是求的零點。
套多項式牛頓迭代式得:
把代入,得:
注意這裏的函數的自變量是一個多項式,因變量也是。
的話其實算作常數,求導之後是沒有的。
因爲自變量是多項式,所以不算複合函數求導,只是對求導,。
每層需要用到和一次乘法,複雜度爲次dft
模板:
#include<ctime>
#include<vector>
#include<cstdio>
#include<algorithm>
#define ll long long
#define pp printf
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i < B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define pb push_back
#define si size()
using namespace std;
typedef vector<ll> V;
const int N = (1 << 19) + 5;
const int mo = 998244353;
const int ni2 = (mo + 1) / 2;
ll ksm(ll x, ll y) {
ll s = 1;
for(; y; y /= 2, x = x * x % mo)
if(y & 1) s = s * x % mo;
return s;
}
int r[N]; ll aa[N];
void dft(V &b, int f) {
int n = b.si;
ff(i, 0, n) aa[i] = b[i];
ff(i, 0, n) {
r[i] = r[i / 2] / 2 + (i & 1) * (n / 2);
if(i < r[i]) swap(aa[i], aa[r[i]]);
}
for(int h = 1; h < n; h *= 2) {
ll wn = ksm(ksm(3, (mo - 1) / 2 / h), f == -1 ? mo - 2 : 1);
for(int j = 0; j < n; j += 2 * h) {
ll A, W = 1, *l = aa + j, *r = aa + j + h;
ff(i, 0, h) {
A = *r * W, *r = (*l - A) % mo, *l = (*l + A) % mo;
W = W * wn % mo; l ++; r ++;
}
}
}
if(f == -1) {
ll v = ksm(n, mo - 2);
ff(i, 0, n) aa[i] = (aa[i] + mo) * v % mo;
}
ff(i, 0, n) b[i] = aa[i];
}
V operator * (V a, V b) {
int z = a.si + b.si - 1;
int n = 1; while(n < z) n *= 2;
a.resize(n); b.resize(n);
dft(a, 1); dft(b, 1);
ff(i, 0, n) a[i] = a[i] * b[i] % mo;
dft(a, -1); a.resize(z); return a;
}
V operator + (V a, V b) {
int sz = max(a.si, b.si); a.resize(sz); b.resize(sz);
ff(i, 0, sz) a[i] = (a[i] + b[i] + mo) % mo;
return a;
}
V operator - (V a, V b) {
int sz = max(a.si, b.si); a.resize(sz); b.resize(sz);
ff(i, 0, sz) a[i] = (a[i] - b[i] + mo) % mo;
return a;
}
V qni(V a) {
int n0 = 1; while(n0 < a.si) n0 *= 2;
V b; b.clear(); b.pb(ksm(a[0], mo - 2));
for(int n = 2; n <= n0; n *= 2) {
b.resize(n * 2); dft(b, 1);
V c = a; c.resize(n); c.resize(n * 2); dft(c, 1);
ff(i, 0, n * 2) b[i] = (b[i] * 2 - b[i] * b[i] % mo * c[i]) % mo;
dft(b, -1); b.resize(n);
}
b.resize(a.si); return b;
}
void fan(V &a) {
reverse(a.begin(), a.end());
}
V div(V a, V b) {
int n = a.size() - b.size() + 1;
fan(a); fan(b);
b.resize(a.size()); b = qni(b);
a = a * b; a.resize(n);
fan(a);
return a;
}
V qmo(V a, V b) {
return a - (b * (div(a, b)));
}
ll w;
struct P {
ll x, y;
P(){}
P(ll _x, ll _y) {x = _x, y = _y;}
};
P operator *(P a, P b) { return P((a.x * b.x + a.y * b.y % mo * w) % mo, (a.x * b.y + a.y * b.x) % mo);}
int pd(ll x) {
return ksm(x, (mo - 1) / 2) == 1;
}
P ksm(P x, ll y) {
P s = P(1, 0);
for(; y; y /= 2, x = x * x)
if(y & 1) s = s * x;
return s;
}
ll Sqrt(ll n) {
if(!n) return 0;
while(1) {
ll a = rand() % mo;
w = (a * a - n + mo) % mo;
if(pd(w)) continue;
return ksm(P(a, 1), (mo + 1) / 2).x;
}
}
V Sqrt(V a) {
int n0 = 1; while(n0 < a.si) n0 *= 2;
V b; b.clear(); b.push_back(Sqrt(a[0])); b[0] = 1;
for(int n = 2; n <= n0; n *= 2) {
V c = a; c.resize(n);
V d = b; d.resize(n);
ff(i, 0, d.si) d[i] = d[i] * 2 % mo;
ff(i, 0, b.si) b[i] = b[i] * ni2 % mo;
c = c * qni(d);
b = b + c;
}
b.resize(n0); return b;
}
V qd(V a) {
a[0] = 0;
ff(i, 1, a.si) a[i - 1] = a[i] * i % mo;
return a;
}
V jf(V a) {
a.pb(0);
fd(i, a.si, 1) a[i] = a[i - 1] * ksm(i, mo - 2) % mo;
a[0] = 0;
return a;
}
V ln(V a) {
int sa = a.si;
V b = a; b = qni(b); a = qd(a);
a = a * b; a = jf(a); a.resize(sa);
return a;
}
V exp(V a) {
int n0 = 1; while(n0 < a.si) n0 *= 2;
V b; b.clear(); b.pb(1);
for(int n = 2; n <= n0; n *= 2) {
V c = b; c.resize(n); c = ln(c);
V d = a; d.resize(n);
c = c - d;
c.resize(2 * n); dft(c, 1);
b.resize(2 * n); dft(b, 1);
ff(i, 0, 2 * n) b[i] = (b[i] - c[i] * b[i]) % mo;
dft(b, -1); b.resize(n);
}
b.resize(a.si);
return b;
}
int n, m;
V a, b;
int main() {
srand(time (0));
}