多项式基础1 FFT NTT FWT

本着不在NOIP前碰多项式的原则,我现在开始学FFT。

多项式的表示

系数表示: f ( x ) = ∑ i = 0 n a i x i f(x)=\sum_{i=0}^na_ix^i f(x)=i=0naixi,则 a a a f f f的系数表示。

点值表示:一个长为 n + 1 n+1 n+1的数组,其中元素为 ( d i , f ( d i ) ) (d_i,f(d_i)) (di,f(di)) d d d是元素互不相同的数组。

可以使用DFTIDFT O ( n log ⁡ n ) O(n\log n) O(nlogn)复杂度内将系数表示与点值表示互相转换。

k k k次单位根

k k k次单位根是其 k k k次幂为 1 1 1的一些复数,共有 k k k个,分别为 e 2 π i j k ( j ∈ [ 0 , k − 1 ] ) e^\frac{2\pi i j}{k}(j\in [0,k-1]) ek2πij(j[0,k1])。记 ω k = e 2 π i k \omega_k=e^\frac{2\pi i}{k} ωk=ek2πi,则 k k k次单位根可分别表示为 w k i ( i ∈ [ 0 , k − 1 ] ) w_k^i(i\in[0,k-1]) wki(i[0,k1])

在复平面上, k k k次单位根是单位圆上均匀分布的 k k k个点,其中第 0 0 0个在 ( 1 , 0 ) (1,0) (1,0),接下来的依次按逆时针绕一圈。容易想到单位根有以下性质:

  • ω k i = ω k 2 i 2 \omega_k^i=\omega_{\frac{k}{2}}^\frac{i}{2} ωki=ω2k2i
  • ω k i = − ω k i + k 2 \omega_k^i=-\omega_k^{i+\frac{k}{2}} ωki=ωki+2k
  • ω k i = cos ⁡ i k 2 π + i ⋅ sin ⁡ i k 2 π \omega_k^i=\cos\frac{i}{k}2\pi+i\cdot\sin\frac{i}{k}2\pi ωki=coski2π+isinki2π

DFT

用于将系数表示转化为点值表示。

先将 f ( x ) f(x) f(x)的阶向上扩充为 2 k − 1 2^k-1 2k1(多余部分的 a i = 0 a_i=0 ai=0)。设此时 f ( x ) f(x) f(x)的阶为 n − 1 n-1 n1,考虑将 n n n次单位根作为 d d d数组,即 d i = ω n i d_i=\omega_n^i di=ωni

f 0 ( x ) = a 0 x 0 + a 2 x 1 + ⋯ + a n − 1 x n 2 f_0(x)=a_0x^0+a_2x^1+\cdots+a_{n-1}x^{\frac{n}{2}} f0(x)=a0x0+a2x1++an1x2n f 1 ( x ) = a 1 x 0 + a 3 x 1 + ⋯ + a n − 2 x n 2 f_1(x)=a_1x^0+a_3x^1+\cdots+a_{n-2}x^{\frac{n}{2}} f1(x)=a1x0+a3x1++an2x2n。显然 f ( x ) = f 0 ( x 2 ) + x f 1 ( x 2 ) f(x)=f_0(x^2)+xf_1(x^2) f(x)=f0(x2)+xf1(x2)

于是 f ( ω n i ) = f 0 ( ω n 2 i ) + ω n i f 1 ( ω n 2 i ) = f 0 ( ω n 2 i ) + ω n i f 1 ( ω n 2 i ) f(\omega_n^i)=f_0(\omega_n^{2i})+\omega_n^if_1(\omega_n^{2i})=f_0(\omega_\frac{n}{2}^i)+\omega_n^if_1(\omega_\frac{n}{2}^i) f(ωni)=f0(ωn2i)+ωnif1(ωn2i)=f0(ω2ni)+ωnif1(ω2ni)

同理可以得到 f ( ω n i + n 2 ) = f ( − ω n i ) = f 0 ( ω n 2 i ) − ω n i f 1 ( ω n 2 i ) f(\omega_n^{i+\frac{n}{2}})=f(-\omega_n^i)=f_0(\omega_\frac{n}{2}^i)-\omega_n^if_1(\omega_\frac{n}{2}^i) f(ωni+2n)=f(ωni)=f0(ω2ni)ωnif1(ω2ni)

看到没? f 0 f_0 f0 f 1 f_1 f1中带入的值是一样的,因此我们可以分治递归解决问题。复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

IDFT

用于将点值表示转化为系数表示。该过程等价于用 ω n i + n 2 \omega_n^{i+\frac{n}{2}} ωni+2n替换 ω n i \omega_n^i ωni做DFT,再将结果除以 n n n。复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

DFT和IDFT统称为FFT(Fa Fa Tower)。实际实现中,应用雷德(Rader)算法来优化常数,用迭代模拟递归的过程。即先枚举层,再枚举每个“计算块”。数组下标需要进行变换成如 { 0 , 4 , 2 , 6 , 1 , 5 , 3 , 7 } \{0,4,2,6,1,5,3,7\} {0,4,2,6,1,5,3,7}这样。
求解过程

const double pi = 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899;
struct complex {
    double r, v;
    complex() {}
    complex(double rr, double vv) {r = rr; v = vv;} 
};
inline complex operator + (complex a, complex b) {
    return complex(a.r+b.r, a.v+b.v);
}
inline complex operator - (complex a, complex b) {
    return complex(a.r-b.r, a.v-b.v);
}
inline complex operator - (complex x) {
    return complex(-x.r, -x.v);
}
inline complex operator * (complex a, complex b) {
    return complex(a.r*b.r-a.v*b.v, a.r*b.v+a.v*b.r);
}

const int maxn = 1<<17;
void fft(complex a[], int n, bool typ) {
    for(int i = 1, j = n>>1; i < n; i++) {
        if(i < j) swap(a[i], a[j]);
        for(int k = n>>1; (j^=k) < k; k >>= 1);
    }
    for(int i = 1; i < n; i <<= 1) for(int j = 0; j < n; j += i<<1) {
        complex w = complex(1, 0), wn = complex(cos(pi/(double)i), (typ?1:-1)*sin(pi/(double)i));
        for(int k = 0; k < i; k++) {
            complex u = a[j+k], v = w * a[j+i+k];
            a[j+k] = u + v;
            a[j+i+k] = u - v;
            w = w * wn;
        }
    }
    if(!typ) for(int i = 0; i < n; i++) a[i].r /= n;
}
complex tmp[maxn<<1];
void Mul(complex a[], int an, complex b[], int bn) {
    if(an <= 48 || bn <= 48) {
        for(int i = 0; i < an+bn-1; i++) tmp[i] = complex(0, 0);
        for(int i = 0; i < an; i++) for(int j = 0; j < bn; j++) tmp[i+j] = tmp[i+j] + a[i] * b[j];
        for(int i = 0; i < an+bn-1; i++) a[i] = tmp[i];
        return;
    }
    int n = 1;
    while(n < an+bn-1) n <<= 1;
    fft(a, n, 1); fft(b, n, 1);
    for(int i = 0; i < n; i++) a[i] = a[i] * b[i];
    fft(a, n, 0);
}

卷积

即多项式乘法。多项式 f f f g g g的卷积为 h h h,设 f f f的阶为 n n n g g g的阶为 m m m,则 h h h的阶为 n + m n+m n+m。设 f f f g g g的系数数组分别为 a a a b b b,则 h h h的系数数组 c c c满足 c i = ∑ j = 0 i a j b i − j c_i=\sum_{j=0}^ia_jb_{i-j} ci=j=0iajbij。也可理解为 a i a_i ai b j b_j bj c i + j c_{i+j} ci+j + a i b j +a_ib_j +aibj的贡献。

可以看到朴素的卷积算法复杂度为 O ( n m ) O(nm) O(nm)。但若先将 f f f g g g转化为点值表示后再相乘,则乘法的复杂度直接降为 O ( n + m ) O(n+m) O(n+m)。因此卷积的复杂度等于FFT的复杂度,即 O ( n log ⁡ n ) O(n\log n) O(nlogn)

NTT

用于解决模意义下的系数表示与点值表示相转化。设模数为 P P P

由于 ω n n ≡ g P − 1 ≡ 1 ( m o d P ) \omega_n^n\equiv g^{P-1}\equiv1\pmod P ωnngP11(modP),因此 ω n i ≡ g i ⋅ ( P − 1 ) n \omega_n^i\equiv g^\frac{i\cdot(P-1)}{n} ωnigni(P1)。将原来的单位根换为原根即可。

const int maxn = 1<<17;
int w[2][1<<19];
void nttinit() {
    for(int i = 0; i <= 18; i++) {
        w[1][1<<i] = w[0][1<<i] = 1;
        int wn = qpow(gmd, md-1>>i+1), invwn = inv(wn);
        for(int j = (1<<i)+1; j < 1<<i+1; j++) {
            w[1][j] = mul(w[1][j-1], wn);
            w[0][j] = mul(w[0][j-1], invwn);
        }
    }
}
void ntt(int a[], int n, bool typ) {
    for(int i = 1, j = n>>1; i < n; i++) {
        if(i < j) swap(a[i], a[j]);
        for(int k = n>>1; (j^=k) < k; k >>= 1);
    }
    for(int i = 1; i < n; i <<= 1) for(int j = 0; j < n; j += i<<1) for(int k = 0; k < i; k++) {
        int u = a[j+k], v = mul(w[typ][i+k], a[j+i+k]);
        a[j+k] = add(u, v);
        a[j+i+k] = sub(u, v);
    }
    if(!typ) {
        int invn = inv(n);
        for(int i = 0; i < n; i++) f[i] = mul(f[i], invn);
    }
}
int tmp[maxn<<1];
void Mul(int a[], int an, int b[], int bn) {
    if(an <= 48 || bn <= 48) {
        memset(tmp, 0, (an+bn-1)*sizeof(int));
        for(int i = 0; i < an; i++) for(int j = 0; j < bn; j++) Add(tmp[i+j], mul(a[i], b[j]));
        memcpy(a, tmp, (an+bn-1)*sizeof(int));
        return;
    }
    int n = 1;
    while(n < an+bn-1) n <<= 1;
    ntt(a, n, 1); ntt(b, n, 1);
    for(int i = 0; i < n; i++) a[i] = mul(a[i], b[i]);
    ntt(a, n, 0);
}

采用vector的版本:

typedef vector<int> pol;

void ntt(pol& f, int typ) {
    int n = f.size();
    for(int i = 1, j = n>>1; i < n; i++) {
        if(i < j) swap(f[i], f[j]);
        for(int k = n>>1; (j ^= k) < k; k >>= 1);
    }
    for(int i = 1; i < n; i <<= 1) for(int j = 0; j < n; j += i<<1) for(int k = 0; k < i; k++) {
        int u = f[j+k], v = mul(w[typ][i+k], f[j+i+k]);
        f[j+k] = add(u, v);
        f[j+i+k] = sub(u, v);
    }
    if(!typ) {
        int invn = inv(n);
        for(int i = 0; i < n; i++) f[i] = mul(f[i], invn);
    }
}

FWT

用于求位运算(集合)卷积。同样我们先对原来的两个数组进行处理,再 O ( n ) O(n) O(n)相乘,再逆变换回去。

或FWT

考虑对数组的一种变换 F W T ( A ) = B FWT(A)=B FWT(A)=B,满足 B [ i ] = ∑ i ∣ j = i A [ j ] B[i]=\sum_{i|j=i}A[j] B[i]=ij=iA[j]。这个可以通过分治在 O ( n log ⁡ n ) O(n\log n) O(nlogn)时间内做到。

将数组的长度向上扩充到 2 k 2^k 2k,设其为 n n n。假设当前我们在处理区间 [ l , r ] [l,r] [l,r],并且已经计算完了 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]自己区间内部的影响,考虑合并。我们发现 l + i l+i l+i这个数及其二进制子集一定是 m i d + i + 1 mid+i+1 mid+i+1这个数的二进制子集,因此对于所有 i ∈ [ 0 , r − l + 1 2 ] i\in[0,\frac{r-l+1}{2}] i[0,2rl+1] l + i l+i l+i的答案累加到 m i d + i + 1 mid+i+1 mid+i+1上即可。即 F W T ( L : R ) = F W T ( L ) : ( F W T ( L ) + F W T ( R ) ) FWT(L:R)=FWT(L):(FWT(L)+FWT(R)) FWT(L:R)=FWT(L):(FWT(L)+FWT(R)),其中 : : :表示连接。不难推出其逆变换为 U F W T ( L : R ) = U F W T ( L ) : ( U F W T ( R ) − U F W T ( L ) ) UFWT(L:R)=UFWT(L):(UFWT(R)-UFWT(L)) UFWT(L:R)=UFWT(L):(UFWT(R)UFWT(L))

与FWT

此时需要实现的变换是 B [ i ] = ∑ i &amp; j = i A [ j ] B[i]=\sum_{i\&amp; j=i}A[j] B[i]=i&j=iA[j]

与或运算类似,容易推出 F W T ( L : R ) = ( F W T ( L ) + F W T ( R ) ) : F W T ( R ) FWT(L:R)=(FWT(L)+FWT(R)):FWT(R) FWT(L:R)=(FWT(L)+FWT(R)):FWT(R) U F W T ( L : R ) = ( U F W T ( L ) − U F W T ( R ) ) : U F W T ( R ) UFWT(L:R)=(UFWT(L)-UFWT(R)):UFWT(R) UFWT(L:R)=(UFWT(L)UFWT(R)):UFWT(R)

以上两变换又称FMT(快速莫比乌斯变换),又称高维前缀和。

异或FWT

这个比较奇怪,反正我想不到。此时需要实现的变换是 B [ i ] = ∑ ∣ i ∩ j ∣ ≡ 0 ( m o d 2 ) A [ j ] − ∑ ∣ i ∩ j ∣ ≡ 1 ( m o d 2 ) A [ j ] B[i]=\sum_{|i\cap j|\equiv 0\pmod 2}A[j]-\sum_{|i\cap j|\equiv 1\pmod 2}A[j] B[i]=ij0(mod2)A[j]ij1(mod2)A[j]

变换是 F W T ( L : R ) = ( F W T ( L ) + F W T ( R ) ) : ( F W T ( L ) − F W T ( R ) ) FWT(L:R)=(FWT(L)+FWT(R)):(FWT(L)-FWT(R)) FWT(L:R)=(FWT(L)+FWT(R)):(FWT(L)FWT(R)) U F W T ( L : R ) = U F W T ( L ) + U F W T ( R ) 2 : U F W T ( L ) − U F W T ( R ) 2 UFWT(L:R)=\frac{UFWT(L)+UFWT(R)}{2}:\frac{UFWT(L)-UFWT(R)}{2} UFWT(L:R)=2UFWT(L)+UFWT(R):2UFWT(L)UFWT(R)(和差问题)。手推可验证正确。
*2018.11~12

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值