1. 程式人生 > >FFT總結(多項式的構造方法)

FFT總結(多項式的構造方法)

void 利用 一個 總結 經典 lock names for i+1

前言.FFT算法

網上有很多,這裏不再贅述。
模板見我的代碼庫:戳我

正經向:FFT題目解題思路

\(FFT\)這個玩意不可能直接裸考的.....
其實一般\(FFT\)的題目難點不在於\(FFT\),而在於構造多項式與卷積。
兩個經典例題:

[ZJOI2014]力
給定序列\(\{ q[1],q[2],....q[n]\}\)
定義:\(Ej = \sum_{i<j} \frac{q[i]}{(i-j)^2} - \sum_{i>j} \frac{q[i]}{(i-j)^2}\)
\(E_1,E_2,E_3....E_{n-1},E_n\) , 數據範圍:\(n\leq 10^5\)

.

[HNOI2017]禮物
給定兩個節點帶權的環,
\(<\ x_1,x_2,...x_n\ >\)\(<\ y_1,y_2,...y_n\ >\)
現在你可以任意對齊,
對齊後,此時代價為\(\sum_{i=1}^n x_i y_j\) , 其中\(x_i\)\(y_j\)對齊。
請求方案最小化此代價,數據範圍\(n\leq 5\times 10^4\)

下文中,我們以 [HNOI2017]禮物 為主講題, [ZJOI2014]對照訓練。
下文中,為了方便描述,
我們用花括號表示 多項式系數元素 ,
如:\(f<x> = \{a_0,a_1,a_2,...,a_{n-1}\}\)


我們用中括號表示 多項式系數,
如:\(f(x)=a_0+a_1x+a_2x^2+.....+a_n x^n = [a_0,a_1,a_2,.....,a_n]\)
.
一般遇到這種構造多項式的題目,分三步走。

Step1 定元素

首先確定多項式系數元素是什麽。
比如禮物這一題,我們假設第一個串的\(x_1\)對應第二個串的\(y_k\),那麽:
\[Val = \sum_{i=1}^{n-k+1} x_i y_{i+k-1} \ + \ \sum_{i=1}^{k-1} x_{i+(n-k+1)} y_i \ ; \]
顯然前後兩項分開考慮一下,下文中以分析\(\sum_{i=1}^{n-k+1} x_i y_{i+k-1}\)

為例
我們要讓\(\sum_{i=1}^{n-k+1} x_i y_{i+k-1}\)成為卷積的一個系數。
顯然多項式的系數元素為:
\(f1 = \{ x_1,x_2,x_3.....x_n\}\) \(\ \ \ \ \ \ \ \ \ \ \)\(f2 = \{ y_1,y_2,y_3.....y_n\}\)

[ZJOI2014]力 這題類似,可以得到:
\(f1 = \{ q[1],q[2],q[3].....q[n]\}\)
\(f2 = \{ \frac{1}{1^2},\frac{1}{2^2},\frac{1}{3^2}.....\frac{1}{(n+1)^2}\}\)

Step2 定順序

顯然多項式元素應該是單調遞增或者單調遞減排列的。
我們的第二步就是確定這個順序。
考慮一下卷積系數的特性,
對於卷積的每一個系數,它的未知數次數來源為兩個多項式。
所以如果我們想要把答案聚集在一個系數上,就應該保證對應元素未知數的次數和不變
那麽只會有兩種情況:
( 1 )形如\(\sum x_i y_{k-i}\) , 此時下標和不變,兩個的順序相同
( 2 )形如\(\sum x_i y_{k+i}\) , 此時下標差不變,兩個的順序相反
利用這個就可以確定我們構造的兩個多項式系數排列順序關系。
看[HNOI2017]禮物這題,\(\sum_{i=1}^{n-k+1} x_i y_{i+k-1}\)這個東西,
兩個元素的下標差相同,所以兩個多項式的系數順序相反,即:
\(f1 = [ x_1,x_2,x_3.....x_n]\) \(\ \ \ \ \ \ \ \ \ \ \)\(f2 = [y_n,y_{n-1},.....y_2,y_1]\)

再看[ZJOI2014]力 :
我們以後面這一項為例:\(\sum_{i<j} \frac{q[i]}{(i-j)^2}\)
隨著\(q[i]\)下標的增加,\(\frac{1}{(i-j)^2}\)下標減小 (\(i-j\)減小)。
所以 兩者下標的和應該不變(或者手玩一把即可),即:
\(f1 = [q[1],q[2],q[3],....q[n]]\)
\(f2 = [\frac{1}{1^2},\frac{1}{2^2},\frac{1}{3^2}....\frac{1}{(n+1)^2}]\)

Step3 定答案

此時構造出來的卷積的某一項系數一定就是此時的對應答案。
關鍵是我們怎麽確定這個答案。
這個其實並沒有什麽技巧,帶特殊值手玩即可。
但是手玩的思路也要清晰,不然玩半天也玩不出來。
下面給一個比較清晰的思路:
.
[HNOI2017]禮物
這題的兩個多項式:
\(f1 = x_1 a^0 +x_2 a^1+x_3 a^2.....x_n a^{n-1}\)
\(f2 = y_n a^0 + y_{n-1} a^1 ,.....y_2 a^{n-2} + y_1 a^{n-1}\)
考慮\(k = 1\)時,\(x_1\)\(y_1\)對應,\(x_2\)\(y_2\)對應.....
顯然此時乘出來的未知數次數為\(n-1\)
考慮\(k = 2\)時,\(x_1\)\(y_2\)對應,\(x_2\)\(y_3\)對應....
顯然此時乘出來的未知數次數為\(n-2\)
手玩出兩個後就可以不玩了,因為答案肯定是連續的一段系數。
綜上所述,當\(k=r\)時,答案系數 對應的未知數次數為 \(n-r\)
.
定答案的時候,一定要註意答案是否有存在的意義
例如 [HNOI2017]禮物這題的另一項:$\sum_{i=1}^{k-1} x_{i+(n-k+1)} y_i $
顯然,當\(k = 1\)的時候是沒有任何意義的。
所以最後統計答案不能統計,從\(2\)開始\(for\)

for(RG int i = 2; i <= N; i ++)ans[i] += (int)(f1[2*N-i].r+0.5);

註意細節,思路清晰,這一步也還是不難的。

[ZJOJ2014]力
\(f1 = q[1]a^0 + q[2]a^1 + q[3]a^2....q[n]a^{n-1}\)
\(f2 = \frac{1}{1^2}a^0 + \frac{1}{2^2}a^1 + \frac{1}{3^2}a^2 ....\frac{1}{(n+1)^2}a^{n-1}\)
\(\sum_{i<j} \frac{q[i]}{(i-j)^2}\)
\(j = 1\)時,顯然無意義舍去。
\(j = 2\)時,\(res = \frac{q[1]}{\frac{1}{1^2}}\) , 未知數次數為 \(0\)
\(j = 3\)時,$res = \frac{q[1]}{\frac{1}{2^2}} + \frac{q[2]}{\frac{1}{1^2}} $ , 未知數次數為 \(1\)
綜上所述,當\(j = r(j \neq 1)\)時 ,對應的未知數次數為 \(r - 2\)

附錄: [HNOI 2017]禮物 的具體實現代碼

#include<bits/stdc++.h>
#define IL inline
#define RG register
#define ll long long
#define _ 300005
using namespace std;
const double PI = acos(-1);

int n,m,N,M,l, a[_] , b[_] ,Ans1,F1,F2,Ans, ans[_] , R[_];
struct Complex{
    double r,i;
    IL Complex(){ r = 0; i = 0; }
    IL Complex(RG double a,RG double b){ r = a; i = b; }
    IL Complex operator + (Complex B){return Complex(r+B.r,i+B.i);}
    IL Complex operator - (Complex B){return Complex(r-B.r,i-B.i);}
    IL Complex operator * (Complex B){
        return Complex(r*B.r - i*B.i , r*B.i + i*B.r);
    }
};
Complex f1[_] , f2[_] , X , Y;

IL void FFT(RG Complex *P , RG int opt){
    for(RG int i = 0; i < n; i ++)
        if(i < R[i]) swap(P[i] , P[R[i]]);
    for(RG int i = 1; i < n; i <<= 1){
        RG Complex W(cos(PI/i),opt*sin(PI/i));
        for(RG int j = 0 , p = i<<1; j < n; j += p){
            RG Complex w(1,0);
            for(RG int k = 0; k < i; k ++,w = w*W){
                X = P[j+k];  Y = w*P[j+k+i];
                P[j+k] = X+Y;  P[j+k+i] = X-Y;
            }
        }
    }
    if(opt == -1)for(RG int i = 0; i < n; i ++)P[i].r /= n;
}

IL void Solve(){
    n = N - 1; m = 2*n; l = 0;
    for(n = 1; n <= m; n<<=1) ++ l;
    for(RG int i = 0; i <= N-1; i ++)
        f1[i].r = a[i+1] , f2[i].r = b[N-i];
    for(RG int i = 0; i < n; i ++)
        R[i] = (R[i>>1]>>1) | ((i&1)<<(l-1));
    FFT(f1,1); FFT(f2,1);
    for(RG int i = 0; i < n; i ++)f1[i] = f1[i]*f2[i];
    FFT(f1,-1);
    for(RG int i = 1; i <= N; i ++)ans[i] = (int)(f1[N-i].r+0.5);
    for(RG int i = 2; i <= N; i ++)ans[i] += (int)(f1[2*N-i].r+0.5);
    Ans1 = 0;
    for(RG int i = 1; i <= N; i ++)Ans1 = max(Ans1,ans[i]);
}

int main(){
    freopen("gift.in","r",stdin);
    cin >> N >> M;
    for(RG int i = 1; i <= N; i ++)cin >> a[i];
    for(RG int j = 1; j <= N; j ++)cin >> b[j];
    Solve();
    for(RG int i = 1; i <= N; i ++)F1 += a[i]*a[i] + b[i]*b[i];
    for(RG int i = 1; i <= N; i ++)F2 += a[i] - b[i];
    Ans = 2147483640;
    for(RG int c = -100; c <= 100; c ++)
        Ans = min(Ans,F1+N*c*c+2*c*F2-2*Ans1);
    cout<<Ans;  return 0;
}

FFT總結(多項式的構造方法)