题目链接:点击打开链接
本题简单剖析出来就是固定其中一个串然后另一个串旋转到一个位置值得答案值最大.我们发现每次求值的复杂度是O(n),一共有n种旋转方法,所以总复杂度是O(n^2),如果把这n种都综合起来的话不就是近似多项式乘法了吗?那么怎么转换呢?看下面的图
以n=5为例,那么5种旋转方式的值分部在多项式的哪里呢?从图看出就是x^4,x^3和x^8,x^2和x^7,x^1和x^6,1和x^5.总结就是除了第n-1项为一个组合外,其他的组合是n-i和2*n-i.那不是就fft了吗,然后找出最大值旋转位置最后暴力求出准确值,因为fft求出来是有误差的.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef complex<double> comp;
const int mx = 3e5 + 10;
const double pai = acos(-1);
comp a[mx],b[mx];
int rev[mx],n,c[mx],d[mx];
void get_rev(int len)
{
//一个数的翻转可以看做把第一位翻到最后一位
//前面len-1位就是i右移一位的翻转后的右移一位两个相加就是了
for(int i=1;i<(1<<len);i++)
rev[i] = (rev[i>>1]>>1)|((i&1)<<(len-1));
}
void fft(comp *p,int len,int v)
{
for(int i=0;i<len;i++)
if(i<rev[i]) swap(p[i],p[rev[i]]);
for(int i=1;i<len;i<<=1)//模拟递归操作i表示子问题大小,实际问题大小是2*i
{
comp tep = exp(comp(0,v*pai/i));
//实际单位复根公式为e^(2π/2*i)因为2消掉了,所以不懂的人有不解,前面说过了实际规模是2*i
for(int j=0;j<len;j+=(i<<1))
{
comp base(1,0);//开始自然是x的0次方
for(int k=j;k<j+i;k++)//为什么只枚举问题的一半呢,因为另一半可以用公式退出
{
comp x = p[k];//i规模问题的x取第k-j个单位复根的值
comp y = base*p[k+i];//另一半
//根据F(x) = G(x^2) + x*H(x^2)
p[k] = x + y;//P[k]表示2*i规模问题的x取第k-j个单位复根的值
p[k+i] = x-y;//根据w(n,k+n/2) = -w(n,k)直接推出
base *= tep;
}
}
}
if(v==-1) for(int i=0;i<len;i++) p[i] /= len;//IDFT还原
}
int main()
{
scanf("%d",&n);
double x;
for(int i=0;i<n;i++){
scanf("%lf",&x);
a[i] = x,c[i] = x;
}
for(int i=0;i<n;i++){
scanf("%lf",&x);
b[i] = x,d[i] = x;
}
int lens = 0,sum;
while((1<<lens)<2*n-1) lens++;//找到第一个2的次方大于等于总长度
sum = (1<<lens);
get_rev(lens);reverse(b,b+n);
fft(a,sum,1);fft(b,sum,1);//DFT
for(int i=0;i<sum;i++) a[i] *= b[i];
fft(a,sum,-1);//IDFT
ll ans = a[n-1].real() + 0.5,ret = 0;
int l = n-2,r = 2*n-2,pos = 0;
while(l>=0){//由于精度问题只能记录最大值位置(因为算出来的答案有精度差),最后在暴力算一次
ll v1 = (a[l].real()+0.5);
v1 += (a[r].real()+0.5);
if(ans<v1){
ans = v1;
pos = n - l - 1;
}
r--,l--;
}
for(int i=0;i<n;i++) ret += 1ll*c[i]*d[(pos+i)%n];
printf("%lld\n",ret);
return 0;
}