[Hnoi2017]礼物,bzoj4827,FFT

本文探讨了一种使用快速傅立叶变换(FFT)解决特定序列操作问题的方法,包括序列旋转和整体常数加法,通过巧妙地将问题转化为卷积形式,实现了高效求解。文章详细介绍了如何通过FFT确定序列间乘积的最大值,并通过枚举策略找到最佳的常数加法,最终目标是最小化序列操作后的平方和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正题

      看题,发现有两个操作:

      1.把第二个序列旋转。

      2.把第一个序列整体+c。

      因为旋转只用一个旋转就够了。

      发现什么。

      (a-b)^2=a^2+b^2-2ab

      所以使xy的和最大即可。

      假设f_i表示第二个序列向有旋转i位的价值。

      那么就有f_i=\sum_{j=1}^n a_j*b_{i+j}

      不是卷积的形式。

      那么怎么办,让F_{n+i}=f_iA_{n-i}=a_i

      那么就有F_{n+i}=\sum_{j=1}^n A_{n-j}*b_{i+j}

      又因为(n-j)+(i+j)=n+i

      所以可以直接用卷积来做。

      其实就相当于把A反过来。

      然后求F_n\to F_{n+n-1}之间的最小值。

      那么现在有个问题就是要整体+c。

      很好做,我们枚举一个c,把它加给第一个序列.

      会发现 \\((a+c)+b)^2=(a+c)^2+b^2-2b(a+c) \\=(a+c)^2+b^2-2(ab+bc)

      那么这就很有趣了,我们现在用FFT来确定了ab最大,又可以通过枚举来知道bc的大小。

      那么每次算出平方和再减去ab+bc的两倍,取一个最小值就可以了。

<FFT的打法略奇特>

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;

struct complex{
	double x,y;
    complex (double xx=0,double yy=0){x=xx,y=yy;}
    complex operator-(const complex b)const {return (complex){x-b.x,y-b.y};}
    complex operator+(const complex b)const {return (complex){x+b.x,y+b.y};}
    complex operator*(const complex b)const {return (complex){x*b.x-y*b.y,x*b.y+b.x*y};}
}a[300010],b[300010];
int d1[100010],d2[100010];
int n,m;
const double Pi=acos(-1.0);
int where[300010];
int limit,l;

void dft(complex *now,int idft){
    for(int i=0;i<limit;i++)
        if(i<where[i]) swap(now[i],now[where[i]]);
    for(int mid=2;mid<=limit;mid<<=1){
        complex wn(cos(2.0*Pi/mid),idft*sin(2.0*Pi/mid));
        for(int i=0;i<limit;i+=mid){
            complex w(1,0);
            for(int x=i,y=i+mid/2;x<i+mid/2;x++,w=w*wn,y++){
                complex a=now[x],b=w*now[y];
                now[x]=a+b;
                now[y]=a-b;
            }
        }
    }
}

int main(){
	scanf("%d %d",&n,&m);
	int sum=0;
	for(int i=n-1;i>=0;i--) scanf("%d",&d1[i]),a[i].x=d1[i];
	for(int i=1;i<=n;i++) scanf("%d",&d2[i]),b[i+n].x=b[i].x=d2[i],sum+=d2[i];
	limit=1,l=0;
	while(limit<3*n+1) limit*=2,l++;
	for(int i=0;i<limit;i++) where[i]=(where[i>>1]>>1) | ((i&1)<<(l-1));
	dft(a,1);
	dft(b,1);
	for(int i=0;i<=limit;i++) a[i]=a[i]*b[i];
	dft(a,-1);
	int ans=2147483647;
	int mmax=0;
	for(int i=n;i<=2*n-1;i++)
		mmax=max(mmax,(int)(a[i].x/limit+0.5));
	int now=0,dat=0;
	for(int i=0;i<n;i++) now+=(d1[i]-m)*(d1[i]-m),dat+=2*(d1[i]-m);//计算每次变化加的东西,自己推公式
	for(int i=1;i<=n;i++) now+=d2[i]*d2[i];
	dat+=n;
	for(int i=-m;i<=m;i++){
		ans=min(ans,now-2*(mmax+i*sum));
		now+=dat;
		dat+=2*n;
	}
	printf("%d",ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值