• 多项式全家桶的学习历程


    前言

    如果您在想学 (FFT) 的情况下偶然看到了这篇博文, 那么请移步 这里, 相信能给你更好的帮助。(在我看来写的最好的)


    敏捷开发
    还有一大堆要学的啊gan


    引入

    设整数 (n = 2^k (k in N_+))

    设多项式

    [A(x) = a_0 + a_1x + a_2x^2 + cdots + a_{n-1}x^{n-1} ]

    [= a_0 + a_2x^2 + a_4x^4 + cdots + a_{n-2}x^{n-2} ]

    [+ a_1x + a_3x^3 + a_5x^5 +cdots + a_{n-1}x^{n-1} ]

    整理

    [A(x) = a_0 + a_2x^2 + a_4x^4 +cdots + a_{n-2}x^{n-2} ]

    [+ x(a_1 + a_3x^2 + a_5x^4 +cdots + a_{n-1}x^{n-2}) ]

    [A_1(p) = a_0 + a_2p + a_4p^2 + cdots + a_{n-2}p^{frac{n}2-1} ]

    [A_2(q) = a_1 + a_3q + a_5q^2 + cdots + a_{n-1}q^{frac{n}2-1} ]

    [A(x) = A_1(x^2) +xA_2(x^2) ]


    由单位根的性质

    [(omega_n^k)^2 = omega_n^{2k} = omega_{frac{n}2}^k ]

    故若知 (A_1(omega_{frac{n}2}^k))(A_2(omega_{frac{n}2}^k)), 就可知 (A(omega_n^k))

    再由

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

    [A(-x) = a_0 + a_2x^2 + a_4x^4 +cdots + a_{n-2}x^{n-2} ]

    [- x(a_1 + a_3x^2 + a_5x^4 +cdots + a_{n-1}x^{n-2}) ]

    不难得出

    [A(omega_n^{k+frac{n}2}) = A(-omega_n^k) = A_1(omega_{frac{n}2}^k) -omega_n^kA_2(omega_{frac{n}2}^k) ]

    小结

    若知(A_1(omega_{frac{n}2}^k))(A_2(omega_{frac{n}2}^k))

    可知 (A(omega_n^k))(A(omega_n^{k+frac{n}2}))


    (FFT) 的递归方法

    书接上回。
    若知
    (A_1(omega_{frac{n}2}^0) , A_1(omega_{frac{n}2}^1) , A_1(omega_{frac{n}2}^2), A_1(cdots,omega_{frac{n}2}^{frac{n}2-1}))

    (A_2(omega_{frac{n}2}^0) , A_2(omega_{frac{n}2}^1) , A_2(omega_{frac{n}2}^2), A_2(cdots,omega_{frac{n}2}^{frac{n}2-1}))

    则可以以 (O(n)) 时间算出

    (A(omega_n^0),A(omega_n^1),A(omega_n^2),cdots, A(omega_n^{frac{n}2-1}))

    (A(omega_n^{frac{n}2}),A(omega_n^{frac{n}2+1}),A(omega_n^{frac{n}2+2}),cdots, A(omega_n^{n-1}))

    递归处理?
    递归边界?
    (A_{Omega}(x) = a_{?})

    时间复杂度?
    (T(n)) 为 递归处理 (n) 规模问题的总时间复杂度。
    放到这个问题里,粗略得出

    [T(n) = 2T(frac{n}2) +O(n) ]

    运用主定理得 (其实我没用qwq)

    [T(n) = O(n log_2 n) ]


    两个点值表示法多项式的相乘

    [C(x) = A(x)*B(x) ]

    展开下就能证,没了。


    (IFFT)

    从前有一个结论。

    假设多项式 (C(x)) 的点值表示法是

    [(omega_n^0, c_0), (omega_n^1, c_1), (omega_n^2, c_2), cdots, (omega_n^{n-1}, c_{n-1}) ]

    设多项式

    [B(x) = c_0 +c_1x +c_2x^2 +cdots + c_{n-1}x^{n-1} ]

    那么,
    (C(x)) 的系数表示法就是

    [C(x) = B((omega_n^0)^{-1}) + B((omega_n^1)^{-1})x + B((omega_n^2)^{-1})x^2 + cdots + B((omega_n^{n-1})^{-1})x^{n-1} ]

    ((omega_n^k)^{-1}) 表示 (omega_n^k) 的共轭复数。
    同样可以用 (FFT) 搞。
    不想证qwq。


    迭代优化

    好像也叫 蝶形变换(这名字帅啊)
    不想在这写。
    就是减少常数的方法((faster ; !)
    尝试理解的时候注意二进制的翻转一定是在二进制数的长度已经被定义的前提下被定义的即可。
    我的一篇专门介绍二进制翻转的博文


    板子?
    交板子顺便看神犇们超神运行速度的地方

    //建议手写 complex
    //建议w_n^k由(w_n^1)^k 生成, 会快很多很多很多
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = (1<<21)+15;
    const double pi = acos(-1);
    struct cp{
    	double x,y;
    	cp(double x,double y) {
    		this->x=x;
    		this->y=y;
    	}
    	cp() {
    		x=y=0.0;
    	}
    	const cp operator+(const cp p) const{
    		return cp(x+p.x,y+p.y);
    	}
    	const cp operator-(const cp p) const{
    		return cp(x-p.x,y-p.y);
    	}
    	const cp operator*(const cp p) const{
    		//a bi c di
    		//ac-bd ad+bc
    		return cp(x*p.x-y*p.y,x*p.y+y*p.x);
    	}
    };
    int read() {
    	int x=0;
    	char c=getchar();
    	while(c<'0'||c>'9') c=getchar();
    	while(c>='0'&&c<='9') {
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	return x;
    }
    
    int rv[maxn];
    void fft(cp *a, int n, int inv) {
    	for(int i=0;i<n;++i) if(i<=rv[i]) swap(a[i],a[rv[i]]);
    	for(int m=2;m<=n;m<<=1) {
    		cp w(cos(2*pi/m), inv*sin(2*pi/m));
    		for(int i=0;i<n;i+=m) {
    			cp tmp(1,0);
    			for(int j=0;j<(m>>1);++j) {
    				cp p=a[i+j], q=tmp*a[i+j+(m>>1)];
    				a[i+j]=p+q;
    				a[i+j+(m>>1)]=p-q;
    				tmp=tmp*w;
    			}
    		}
    	}	
    }
    
    int n, m;
    cp a[maxn], b[maxn];
    
    int main()
    {
    	n=read()+1, m=read()+1;
    	for(int i=0;i<n;++i) a[i]=cp((double)read(),0);
    	for(int i=0;i<m;++i) b[i]=cp((double)read(),0);
    	for(m=m+n-1,n=1;n<m;n<<=1);
    	for(int i=0;i<n;++i) rv[i] = (rv[i>>1]>>1)|(i&1?(n>>1):0);
    	fft(a,n,1);
    	fft(b,n,1);
    	for(int i=0;i<n;++i) a[i]=a[i]*b[i];
    	fft(a,n,-1);
    	for(int i=0;i<m;++i) cout<<(int)(a[i].x/n+0.5)<<' ';
    	return 0;
     } 
    

    NTT!

    (NTT) 就是系数能取模的 (FFT), 用一种叫 原根 的东西替代了 (FFT) 里用到的 单位根(NTT) 的应用范围似乎比 (FFT) 更广, 因为好多多项式全家桶类的板子都是要对系数取模, 而且 (NTT) 不用担心精度问题。

    (NTT) 在似乎多项式长度的规模一定的时候对模数有一定的要求
    要求似乎是:假设多项式长度为 (2^n), 则将 ((模数-1)) 质因数分解后, (2) 因子的个数应 (ge n)

    原根为何能代替单位根?
    显然啊

    如何用原根生成类似 (n) 次单位根的东西?

    [omega_n^1 = g^{lfloor frac{p-1}n floor} ag{$mod p$} ]

    (在代码里是整除所以我就写整除了)
    (frac{p-1}n) 就是可能的对模数的要求的来源吗?

    类似共轭复数的东西?

    [-omega_n^1 = inv(g)^{lfloor frac{p-1}n floor} ag{$mod p$} ]

    板子?
    Luogu、UOJ 多项式乘法模板可过

    #include<bits/stdc++.h>
    using namespace std;
    inline int read() { 
        char c = getchar(); int x = 0, f = 1;
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    
    #define li long long
    li ksm(li a, li b, li p) {
    	li res = 1ll;
    	for(;b;b>>=1, a=(a*a)%p)
    		if(b&1) res=(res*a)%p;
    	return res%p;
    }
    const li mod = 998244353, g = 3, ig = ksm(g, mod-2, mod);
    
    const int maxn = 3e6+15;
    int n,m;
    li a[maxn], b[maxn];
    
    int rv[maxn];
    void ntt(li *a, int n, int inv) {
    	for(int i=0;i<n;++i) if(i<=rv[i]) swap(a[i], a[rv[i]]);
    	for(int m=2;m<=n;m<<=1) {
    		li w = ksm(inv==1?g:ig, (mod-1)/m, mod);
    		for(int i=0;i<n;i+=m) {
    			li tmp = 1;
    			for(int j=0;j<(m>>1);++j, tmp=(tmp*w)%mod) {
    				li p=a[i+j], q=tmp*a[i+j+(m>>1)]%mod;
    				a[i+j]=(p+q)%mod, a[i+j+(m>>1)]=(p-q+mod)%mod;
    			}
    		}
    	}
    }
    int main()
    {
    	n = read()+1, m = read()+1;
    	for(int i=0;i<n;++i) a[i]=0ll+read();
    	for(int i=0;i<m;++i) b[i]=0ll+read();
    	for(m=n+m-1,n=1;n<m;n<<=1);
    	for(int i=0;i<n;++i) rv[i] = (rv[i>>1]>>1)|((i&1)?(n>>1):0);
    	ntt(a,n,1), ntt(b,n,1);
    	for(int i=0;i<n;++i) a[i] = (a[i]*b[i])%mod;
    	ntt(a,n,-1);
    	li INV = ksm(n,mod-2,mod);
    	for(int i=0;i<m;++i) printf("%lld ", a[i]*INV%mod);
    	return 0;
    }
    
  • 相关阅读:
    websocket协议
    vue组件之间的传值
    vue中非父子组件的传值bus的使用
    $.proxy的使用
    弹性盒模型display:flex
    箭头函数与普通函数的区别
    粘贴到Excel的图片总是有些轻微变形
    centos rhel 中文输入法的安装
    vi ,默认为 .asm .inc 采用nasm的语法高亮
    how-to-convert-ppk-key-to-openssh-key-under-linux
  • 原文地址:https://www.cnblogs.com/tztqwq/p/12863222.html
Copyright © 2020-2023  润新知