【洛谷3723】[AH2017/HNOI2017] 礼物(FFT)

点此看题面

大致题意: 有两个长度为\(n\)的序列,可将其中一个序列全加上一个相同的非负整数\(c\),也可将其中一个序列旋转任意位。求修改后\(\sum_{i=1}^n(x_i-y_i)^2\)的最小值。

前言

出于个人习惯,此处将下标改为从\(0\)开始,即将题目改成求\(\sum_{i=0}^{n-1}(x_i-y_i)^2\)的最小值。

这是我自己推出来的第一道\(FFT\)题目,感觉还是挺有成就感的......

推式子

我们可以假设\(x_i\)变成了\(x_{(i+p)\%n}+c\)

注意这里\(c\)的取值范围,尽管题目中强调\(c\)是非负整数,但由于是可以给\(x\)\(y\)中任意一个序列加上\(c\),所以如果给\(x_i\)全加上一个负整数\(c\),就相当于给\(y_i\)全加上一个非负整数\(-c\),因此,\(c\)可以为任意整数

则此时的答案为:

\[\sum_{i=0}^{n-1}(x_{(i+p)\%n}-y_i+c)^2 \]

根据公式\((a+b+c)^2=a^2+b^2+c^2+2ab+2ac+2bc\),我们可以把平方项拆开得到:

\[\sum_{i=0}^{n-1}(x_{(i+p)\%n}^2+y_i^2+c^2-2x_{(i+p)\%n}y_i+2x_{(i+p)\%n}c-2y_ic) \]

把这个式子的各项分开再整理,得到:

\[\sum_{i=0}^{n-1}(x_{(i+p)\%n}^2+y_i^2)+(nc^2+c\sum_{i=0}^{n-1}(2x_{(i+p)\%n}-2y_i))-2\sum_{i=0}^{n-1}x_{(i+p)\%n}y_i \]

然后我们可以发现,在这个式子的第一项中的\(\sum_{i=0}^{n-1}x_{(i+p)\%n}^2=\sum_{i=0}^{n-1}x_i^2\),第二项中的\(\sum_{i=0}^{n-1}x_{(i+p)\%n}=\sum_{i=0}^{n-1}x_i\),所以就得到:

\[\sum_{i=0}^{n-1}(x_i^2+y_i^2)+(nc^2+c\sum_{i=0}^{n-1}(2x_i-2y_i))-2\sum_{i=0}^{n-1}x_{(i+p)\%n}y_i \]

在这个式子中,我们可以发现:

  1. 第一项是定值。

  2. 第二项是一个关于\(c\)的二次函数,其中各项系数也是确定的,可以直接套公式求出。

    由于\(a=n>0\),所以函数开口向上,在顶点处取最小值。

    而顶点横坐标(即\(c\)的值)为\(-\frac b{2a}\),可以直接计算。这里注意\(c\)是整数,所以要将其四舍五入。

    然后把求出的\(c\)的值代入函数就可以求得这一项的最小值。

  3. 要使第三项最小,其实就相当于使\(\sum_{i=0}^{n-1}x_{(i+p)\%n}y_i\)最大。

    如果我们将\(x_i\)复制一遍,即令\(x_{n+i}=x_i\),并设\(y_i=y'_{n-i-1}\),则可以推得新式子:\(\sum_{i=0}^{n-1}x_{i+p}y'_{n-i-1}\)

    这显然就是一个卷积形式,可以转化为\(\sum_{i+j=n+p-1}x_iy'_j\)

    因此直接上\(FFT\)就可以了。

具体实现可详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define DB double
using namespace std;
int n,m,a[3*N+5],b[N+5],b_[N+5];
I int Calc(CI x)//计算二次函数nc^2+xc的最小值
{
	DB t=-x/(2.0*n);RI l=floor(t),r=l+1,c=t-l<r-t?l:r;//计算顶点横坐标,然后四舍五入得到c的值
	return n*c*c+x*c;//代入函数求值
}
class Poly
{
	private:
		int P,L,R[6*N+5];DB Pi;
		struct node
		{
			DB x,y;I node(Con DB& a=0,Con DB& b=0):x(a),y(b){}
			I node operator + (Con node& o) Con {return node(x+o.x,y+o.y);}
			I node operator - (Con node& o) Con {return node(x-o.x,y-o.y);}
			I node operator * (Con node& o) Con {return node(x*o.x-y*o.y,x*o.y+y*o.x);}
		}A[6*N+5],B[6*N+5];
		I void T(node *s,CI op)
		{
			RI i,j,k;node U,S,x,y;for(i=0;i^P;++i) i<R[i]&&(x=s[i],s[i]=s[R[i]],s[R[i]]=x,0);
			for(i=1;i^P;i<<=1) for(U=node(cos(Pi/i),op*sin(Pi/i)),j=0;j^P;j+=i<<1)
				for(S=node(1,0),k=0;k^i;++k,S=S*U) s[j+k]=(x=s[j+k])+(y=S*s[i+j+k]),s[i+j+k]=x-y;
		}
	public:
		I Poly() {Pi=acos(-1);}
		I void FFT(CI n,int *a,CI m,int *b)//卷积
		{
			RI i;P=1,L=0;W(P<=n+m-2) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
			for(i=0;i^n;++i) A[i]=node(a[i]);for(i=0;i^m;++i) B[i]=node(b[i]);
			for(T(A,1),T(B,1),i=0;i^P;++i) A[i]=A[i]*B[i];for(T(A,-1),i=0;i<=n+m-2;++i) a[i]=A[i].x/P+0.5;
		}
}P;
int main()
{
	RI i;for(scanf("%d%d",&n,&m),i=0;i^n;++i) scanf("%d",a+i);for(i=0;i^n;++i) scanf("%d",b+i);
	RI t=0,k=0;for(i=0;i^n;++i) t+=a[i]*a[i]+b[i]*b[i],k+=2*a[i]-2*b[i];t+=Calc(k);
	for(i=0;i^n;++i) a[n+i]=a[i],b_[i]=b[n-i-1];P.FFT(2*n,a,n,b_);//将a复制一遍,并求b的倒序数组
	for(k=i=0;i^n;++i) k<a[n+i-1]&&(k=a[n+i-1]);return printf("%d",t-2*k),0;//求最大值,然后计算答案
}
posted @ 2019-12-23 19:21  TheLostWeak  阅读(...)  评论(...编辑  收藏