zoukankan      html  css  js  c++  java
  • FFT NTT总结(多项式的构造方法)

    前言.FFT NTT 算法

    网上有很多,这里不再赘述。
    模板见我的代码库:

    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),那么:

    [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 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;
    }
    

  • 相关阅读:
    Java练习 SDUT-1117_求绝对值(选择结构)
    Java练习 SDUT-2561_九九乘法表
    Java练习 SDUT-1160_某年某月的天数
    HDU-1024_Max Sum Plus Plus
    博客园页面DIY
    JDBC
    JavaSE | Lambda| Optional| Stream API
    JavaSE| 网络编程
    JavaSE| 反射
    JavaSE | IO流
  • 原文地址:https://www.cnblogs.com/GuessYCB/p/8358041.html
Copyright © 2011-2022 走看看