設有兩個n階多項式
A(n)=an-1x^n-1+an-2x^n-2+...+a0
B(n)=bn-1x^n-1+bn-2x^n-2+...+b0
則如何求A(n)與B(n)的乘積?
通常的方法是
C(n)的表達形式是
C(n)=c(2n-2)x^(2n-2)+c(2n-1)x^(2n-1)+...+c0
易知C(n)的第k項系數ck=sigma(ai*b(k-i))(0<=i<=k)
很明顯這個算法是Θ(n^2)
所以我們從多項式的基本表達上去研究
一個(n)階多項式的表達方式有幾種?
最常見的方法就是上文的解析式,也就是系數表達式。f(x)=an-1x^n-1+an-2x^n-2+...+a0
但是這種方法在乘法時的時間復雜度是n^2太慢了!
另一種方法就是點值表達式,這種方法我們初中就學到過——在求解一次函數多項式的解析式(系數表達時),我們只需要找2個點就確定了其結構,在求解二次函數時也需三個點就能求出解析式,那么這個結論能否向高次方程擴張呢?
答案是可以的
猜想:對於n階多項式,我們知道n個數對(xi,yi)且任意xi不相等,就能確定一個n階多項式經過這些點
證明:這是與線性代數相關的命題
{1 x1 x1^2 x1^3... x1^n-1} a0 y1
{1 x2 x2^2 x2^3... x2^n-1} a1 y2
......................................... * =
.........................................
{1 xn xn^2 xn^3... xn^n-1} an yn
其行列式不為0(詳見算導) 所以有且僅有一解;
所以我們可以由一組n個元素的數對(xi,yi)求解出n階多項式的系數表達。這稱為插值。
點對表達式的有點在於若兩個n階多項式相乘,如A(n)*B(n)
設A(n)的點對表達為n個(Axi,Ayi)
B(n)的點對表達為n個(Bxi,Byi)
那么A()與B()的乘積的點值表示就是
n個點(Axi*Bxi,Ayi*byi)
這里確定一個2n的多項式僅用n個點是不夠的
所以上文中把A()和B()都得取2n個點對
現在還有一個插值的逆操作——求值
及知道一個n階多項式的系數表達,如何在θ(n log n)的時間內求出n個元素的數對(xi,yi)?
這里需要用到單位復數根的性質
另單位復數(模長=1)e=cosα+isina;
則e^2=cosα*cosα-sinα*sinα+(2cosα*sinα)i=cos2α+sin2αi
及e的模長不變,角度轉過一倍。
則方程x^n=1的n解是什么
由上述知解的模長必為1,切設其與x軸夾角為α,nα=2kπ。
xi=sin(2πi/n)+cos(2πi/n)i
x1..xn成為n階的單位復數根
於是我們可以取x1,x2..xn這幾個單位復數根,再求出這幾個點時的y值
這個方法可以用遞歸進行,就是傳說中的DFT啦
f(xi)=an-1xi^n-1+an-2xi^n-2+...+a0
定義F'(xi)=a0+a2xi+a4xi^2+...+an-2 x^(n/2-1)
和 F"(xi)=a1+a3xi+a5xi^2+...+an-1 x(n/2-1)
f(xi)=F'(xi^2)+xi*F"(xi^2)
我們還發現其實對於x1..xn這個數在復數系中的位置的個數
x1^2,x2^2..xn^2更少(再轉了α后半圈xi與前半全重合)
甚至有對於even(n),前一個的規模恰好是后一個的兩倍
現在我們要計算x1,x2..xn這n個點的平方在當前系數下的值(就變成了x2,x4..xn這n/2個點,數據規模縮小了一半)
所以T(n)=2T(n/2)+θ(n)(對每個xi利用F'和F“”的值組合出f)
根據主定理可知時間復雜度為n log n
//見算導P533 這里求的是w0..wn-1,與上文中的1..n做出一點調整(wn=w0)還有不用記錄當前傳進去的幾個單位復數根,因為單位復數根的個數與系數向量的模長相等,直接生成即可。
FFT(a)// a is a vector including n elements
{
n=a.length();
if(n==1) return a;
wn=e(2πi/n);//就是最小轉一次需要乘的值
w=1;//方便線掃求出wi
A0=(a0,a1,...,an-2);
A1=(a1,a3,...,an-1);
y0=FFT(A0);
y1=FFT(A1);
// y0,y1 is a vector including n/2 elements就是求出的F',F"啦
for k=0 to n/2-1
y[k]=y0[k]+wy1[k];
y[k+(n/2)]=yk[0]-wy1[k];
w=w*wn;
return y;
}
