前言.FFT NTT 算法
网上有很多,这里不再赘述。
模板见我的代码库:
正经向: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) , 数据范围:(nleq 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)对齐。
请求方案最小化此代价,数据范围(nleq 5 imes 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),那么:
显然前后两项分开考虑一下,下文中以分析(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 eq 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;
}