多项式乘法运算初级版
快速傅里葉變換在信息學(xué)競賽中主要用于求卷積,或者說多項式乘法。我們知道,多項式乘法的普通算法時間復(fù)雜度
是,通過快速傅里葉變換可以使時間降為,那么接下來會詳細(xì)介紹快速傅里葉變換的原理。
?
首先來介紹多項式的兩種表示方法,即系數(shù)表示法和點值表示法。從某種意義上說,這兩種方法是等價的。先設(shè)
?
???
?
?
(1)系數(shù)表示法
?
??? 對于一個次數(shù)界為的多項式來說,其系數(shù)表示法就是一個由系數(shù)組成的向量,很
??? 明顯,這樣的多項式乘法運算的時間復(fù)雜度為。
?
(2)點值表示法
?
????對于一個次數(shù)界為的多項式來說,其點值是個點值對所形成的集合
?
???
?
??? 其中各不相同,并且當(dāng)時,有。可以看出一個多項式可以有多種不同的點值
??? 表示法,而通過這個不同的點值對可以表示一個唯一的多項式。而通過點值表示法來計算多項式的乘法,時間
??? 復(fù)雜度為。
?
??? 從原則上來說,計算多項式的點值是簡單易行的,因為我們只需要先選取個相異的點,然后通過秦九韶算法可
??? 以在時間內(nèi)求出所有的,實際上如果我們的選得巧妙的話,就可以加速這一過程,使其運行時間變
????為。
?
??? 根據(jù)多項式的系數(shù)表示法求其點值表示法的過程稱為求值,而根據(jù)點值表示法求其系數(shù)表示法的過程稱為插值。
?
??? 對于求卷積或者說多項式乘法運算問題,先是通過傅里葉變換對系數(shù)表示法的多項式進(jìn)行求值運算,這一步的時
??? 間復(fù)雜度為,然后在時間內(nèi)進(jìn)行點值相乘,再進(jìn)行插值運算。
?
那么,接下來就是我們今天的重點了,如何高效地對一個多項式進(jìn)行求值運算,即將多項式的表示法變?yōu)辄c值表示法。
?
如果選取單位復(fù)根作為求值點,則可以通過對系數(shù)向量進(jìn)行離散傅里葉變換(DFT),得到相應(yīng)的點值表示。同樣地
也可以通過對點值對進(jìn)行逆DFT運算,獲得相應(yīng)的系數(shù)向量。DFT和逆DFT的時間復(fù)雜度均為。
?
?
一. 求DFT
?
??? 選取次單位復(fù)根作為來求點值是比較巧妙的做法。
??? 次單位復(fù)根是滿足的復(fù)數(shù),次單位復(fù)根恰好有個,它們是,,為
??? 了解釋這一式子,利用復(fù)數(shù)冪的定義,值稱為主次單位根,所有其
??? 它次單位復(fù)根都是的次冪。
?
????個次單位復(fù)根在乘法運算下形成一個群,該群的結(jié)構(gòu)與加法群模相同。
?
??? 接下來認(rèn)識幾個關(guān)于次單位復(fù)根的重要性質(zhì)。
???
??? (1)相消引理
?
????????對于任何整數(shù),有
?
????(2)折半引理
?
????????如果且為偶數(shù),則
?
??? (3)求和引理
?
??????? 對任意整數(shù)和不能被整除的非零整數(shù),有
?
???????? ?
?
?????回顧一下,我們希望計算次數(shù)界為的多項式
?
????
?
?????在處的值,假定是2的冪,因為給定的次數(shù)界總可以增大,如果需要,總可以添加值為零
???? 的新的高階系數(shù)。假定已知的系數(shù)形式為,對,定義結(jié)果
???? 如下
?
????????????????
?
?????向量是系數(shù)向量的離散傅里葉變換,寫作。
?????通過使用一種稱為快速傅里葉變換(FFT)的方法,就可以在時間內(nèi)計算出,而直接
???? 計算的方法所需時間為,FFT主要是利用單位復(fù)根的特殊性質(zhì)。FFT方法運用了分治策略,它用
???? 中偶數(shù)下標(biāo)的系數(shù)與奇數(shù)下標(biāo)的系數(shù),分別定義了兩個新的次數(shù)界為的多項式和
?
?????
?
?????則進(jìn)一步有
?
???? 這樣在處的值得問題就轉(zhuǎn)換為求次數(shù)界為的多項式和在點
???? 處的值。由于在奇偶分類時導(dǎo)致順序發(fā)生變化,所以需要先通過Rader算法進(jìn)行
???? 倒位序,在FFT中最重要的一個操作是蝴蝶操作,通過蝴蝶操作可以將前半部分和后半部分的值求出。
?
題目:http://acm.hdu.edu.cn/showproblem.php?pid=1402
?
題意:大數(shù)乘法,需要用FFT實現(xiàn)。
?
代碼:
#include <iostream> #include <string.h> #include <stdio.h> #include <math.h>using namespace std; const int N = 500005; const double PI = acos(-1.0);struct Virt {double r, i;Virt(double r = 0.0,double i = 0.0){this->r = r;this->i = i;}Virt operator + (const Virt &x){return Virt(r + x.r, i + x.i);}Virt operator - (const Virt &x){return Virt(r - x.r, i - x.i);}Virt operator * (const Virt &x){return Virt(r * x.r - i * x.i, i * x.r + r * x.i);} };//雷德算法--倒位序 void Rader(Virt F[], int len) {int j = len >> 1;for(int i=1; i<len-1; i++){if(i < j) swap(F[i], F[j]);int k = len >> 1;while(j >= k){j -= k;k >>= 1;}if(j < k) j += k;} }//FFT實現(xiàn) void FFT(Virt F[], int len, int on) {Rader(F, len);for(int h=2; h<=len; h<<=1) //分治后計算長度為h的DFT{Virt wn(cos(-on*2*PI/h), sin(-on*2*PI/h)); //單位復(fù)根e^(2*PI/m)用歐拉公式展開for(int j=0; j<len; j+=h){Virt w(1,0); //旋轉(zhuǎn)因子for(int k=j; k<j+h/2; k++){Virt u = F[k];Virt t = w * F[k + h / 2];F[k] = u + t; //蝴蝶合并操作F[k + h / 2] = u - t;w = w * wn; //更新旋轉(zhuǎn)因子}}}if(on == -1)for(int i=0; i<len; i++)F[i].r /= len; }//求卷積 void Conv(Virt a[],Virt b[],int len) {FFT(a,len,1);FFT(b,len,1);for(int i=0; i<len; i++)a[i] = a[i]*b[i];FFT(a,len,-1); }char str1[N],str2[N]; Virt va[N],vb[N]; int result[N]; int len;void Init(char str1[],char str2[]) {int len1 = strlen(str1);int len2 = strlen(str2);len = 1;while(len < 2*len1 || len < 2*len2) len <<= 1;int i;for(i=0; i<len1; i++){va[i].r = str1[len1-i-1] - '0';va[i].i = 0.0;}while(i < len){va[i].r = va[i].i = 0.0;i++;}for(i=0; i<len2; i++){vb[i].r = str2[len2-i-1] - '0';vb[i].i = 0.0;}while(i < len){vb[i].r = vb[i].i = 0.0;i++;} }void Work() {Conv(va,vb,len);for(int i=0; i<len; i++)result[i] = va[i].r+0.5; }void Export() {for(int i=0; i<len; i++){result[i+1] += result[i]/10;result[i] %= 10;}int high = 0;for(int i=len-1; i>=0; i--){if(result[i]){high = i;break;}}for(int i=high; i>=0; i--)printf("%d",result[i]);puts(""); }int main() {while(~scanf("%s%s",str1,str2)){Init(str1,str2);Work();Export();}return 0; }
題目:http://acm.hdu.edu.cn/showproblem.php?pid=4609
?
題意:給定n條長度已知的邊,求能組成多少個三角形。
?
分析:用一個num數(shù)組來記錄次數(shù),比如num[i]表示長度為i的邊有num[i]條。然后對num[]求卷積,除去本身重
???? 復(fù)的和對稱的,然后再整理一下就好了。
?
代碼:
#include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> #include <math.h>using namespace std; typedef long long LL;const int N = 400005; const double PI = acos(-1.0);struct Virt {double r,i;Virt(double r = 0.0,double i = 0.0){this->r = r;this->i = i;}Virt operator + (const Virt &x){return Virt(r+x.r,i+x.i);}Virt operator - (const Virt &x){return Virt(r-x.r,i-x.i);}Virt operator * (const Virt &x){return Virt(r*x.r-i*x.i,i*x.r+r*x.i);} };//雷德算法--倒位序 void Rader(Virt F[],int len) {int j = len >> 1;for(int i=1; i<len-1; i++){if(i < j) swap(F[i], F[j]);int k = len >> 1;while(j >= k){j -= k;k >>= 1;}if(j < k) j += k;} }//FFT實現(xiàn) void FFT(Virt F[],int len,int on) {Rader(F,len);for(int h=2; h<=len; h<<=1) //分治后計算長度為h的DFT{Virt wn(cos(-on*2*PI/h),sin(-on*2*PI/h)); //單位復(fù)根e^(2*PI/m)用歐拉公式展開for(int j=0; j<len; j+=h){Virt w(1,0); //旋轉(zhuǎn)因子for(int k=j; k<j+h/2; k++){Virt u = F[k];Virt t = w*F[k+h/2];F[k] = u+t; //蝴蝶合并操作F[k+h/2] = u-t;w = w*wn; //更新旋轉(zhuǎn)因子}}}if(on == -1)for(int i=0; i<len; i++)F[i].r /= len; }//求卷積 void Conv(Virt F[],int len) {FFT(F,len,1);for(int i=0; i<len; i++)F[i] = F[i]*F[i];FFT(F,len,-1); }int a[N]; Virt F[N]; LL num[N],sum[N]; int len,n;void Init() {memset(num,0,sizeof(num));scanf("%d",&n);for(int i=0; i<n; i++){scanf("%d",&a[i]);num[a[i]]++;}sort(a, a + n);int len1 = a[n-1] + 1;len = 1;while(len < len1*2) len <<= 1;for(int i=0; i<len1; i++)F[i] = Virt(num[i],0);for(int i=len1; i<len; i++)F[i] = Virt(0,0); }void Work() {Conv(F,len);for(int i=0; i<len; i++)num[i] = (LL)(F[i].r+0.5);len = a[n-1]*2;for(int i=0; i<n; i++)num[a[i]+a[i]]--;for(int i=1; i<=len; i++)num[i] >>= 1;sum[0] = 0;for(int i=1; i<=len; i++)sum[i] = sum[i-1] + num[i];LL cnt = 0;for(int i=0; i<n; i++){cnt+=sum[len]-sum[a[i]];//減掉一個取大,一個取小的cnt-=(LL)(n-1-i)*i;//減掉一個取本身,另外一個取其它cnt-=(n-1);//減掉大于它的取兩個的組合cnt-=(LL)(n-1-i)*(n-i-2)/2;}LL tot = (LL)n*(n-1)*(n-2)/6;printf("%.7lf\n",(double)cnt/tot); }int main() {int T;scanf("%d",&T);while(T--){Init();Work();}return 0; }?
?
總結(jié)
以上是生活随笔為你收集整理的多项式乘法运算初级版的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hash冲突处理终极版
- 下一篇: 自然数幂和取模问题进一步探究