fjut 3283 - FFT多项式乘法

题目链接:点击打开链接

本题简单剖析出来就是固定其中一个串然后另一个串旋转到一个位置值得答案值最大.我们发现每次求值的复杂度是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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值