【HNOI 2017】礼物

传送门


Problem

在这里插入图片描述
数据范围: 1 ≤ n ≤ 50000 1≤n≤50000 1n50000 1 ≤ m ≤ 100 1≤m≤100 1m100 1 ≤ a i ≤ m 1≤a_i≤m 1aim


Solution

我们的目标是最小化这个式子:

∑ i = 1 n ( x i − y i + c ) 2 \sum_{i=1}^n(x_i-y_i+c)^2 i=1n(xiyi+c)2

把它拆开就是:

∑ i = 1 n ( x i 2 + y i 2 − 2 x i y i + c 2 + 2 c ( x i − y i ) ) \sum_{i=1}^n(x_i^2+y_i^2-2x_iy_i+c^2+2c(x_i-y_i)) i=1n(xi2+yi22xiyi+c2+2c(xiyi))

那么我们的答案就可以分成三个部分:

  1. ∑ i = 1 n ( x i 2 + y i 2 ) \sum_{i=1}^n(x_i^2+y_i^2) i=1n(xi2+yi2),记为 A,这显然是一个定值,可以直接计算。
  2. ∑ i = 1 n ( c 2 + 2 c ( x i − y i ) ) \sum_{i=1}^n(c^2+2c(x_i-y_i)) i=1n(c2+2c(xiyi)),记为 B,由于 ∑ i = 1 n ( x i − y i ) \sum_{i=1}^n(x_i-y_i) i=1n(xiyi) 是定值,那就可以把它看成关于 c c c 的二次函数,取对称轴附近的几个点(因为 c c c 必须是整数,而对称轴不一定是整数)更新答案即可。
  3. 2 ∑ i = 1 n x i y i 2\sum_{i=1}^nx_iy_i 2i=1nxiyi,记为 C,我们要最大化这个式子。那么将 x x x 翻转,将 y y y 倍长,就是一个卷积的形式了。用 f f t fft fft 解出来,不难发现,对于 ∀ k ∈ [ n + 1 , 2 n ] \forall k\in[n+1,2n] k[n+1,2n],该位置上的值为 ∑ i = 1 n x i × y k − i + 1 \sum_{i=1}^nx_{i}\times y_{k-i+1} i=1nxi×yki+1,这就相当于是旋转,取 m a x max max 就可以了。

可以自己在草稿纸上推一推,更助于自己理解。


Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
#define ll long long
using namespace std;
int n,m,limit=1,pos[N];
const double pi=acos(-1);
struct complex{
	double x,y;
	complex(){}
	complex(double x,double y):x(x),y(y){}
	friend complex operator+(const complex &a,const complex &b)  {return complex(a.x+b.x,a.y+b.y);}
	friend complex operator-(const complex &a,const complex &b)  {return complex(a.x-b.x,a.y-b.y);}
	friend complex operator*(const complex &a,const complex &b)  {return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
	friend complex operator/(const complex &a,double b)  {return complex(a.x/b,a.y/b);}
}a[N],b[N];
int x[N],y[N];
void prework(){
	while(limit<=(n<<2))  limit<<=1;
	for(int i=0;i<limit;++i)
		pos[i]=(pos[i>>1]>>1)|((i&1)*(limit>>1));
}
void DFT(complex f[],int type){
	for(int i=0;i<limit;++i)
		if(pos[i]>i)  swap(f[i],f[pos[i]]);
	for(int mid=1;mid<limit;mid<<=1){
		complex now=complex(cos(pi/mid),type*sin(pi/mid));
		for(int i=0;i<limit;i+=(mid<<1)){
			complex w=complex(1,0);
			for(int j=0;j<mid;++j,w=w*now){
				complex p0=f[i+j],p1=w*f[i+j+mid];
				f[i+j]=p0+p1,f[i+j+mid]=p0-p1;
			}
		}
	}
	if(type==-1)  for(int i=0;i<limit;++i)  f[i].x/=limit;
}
int main(){
	ll A=0,B=0,sum=0;
	scanf("%d%d",&n,&m),prework();
	for(int i=1;i<=n;++i)  scanf("%lf",&a[i].x);
	for(int i=1;i<=n;++i)  scanf("%lf",&b[i].x),b[n+i]=b[i];
	for(int i=1;i<=n;++i)  A+=a[i].x*a[i].x+b[i].x*b[i].x,sum+=a[i].x-b[i].x;
	reverse(a+1,a+n+1);
	ll axis=-sum/n,C=1e18;
	for(int i=-1;i<=1;++i)  C=min(C,n*(axis+i)*(axis+i)+2*(axis+i)*sum);
	DFT(a,1),DFT(b,1);
	for(int i=0;i<limit;++i)  a[i]=a[i]*b[i];
	DFT(a,-1);
	for(int i=1;i<=n;++i)
		B=max(B,(ll)(a[n+i].x+0.5));
	printf("%lld\n",A+C-2*B);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值