标签:FFT
题目
题目描述
我的室友最近喜欢上了一个可爱的小女生。马上就要到她的生日了,他决定买一对情侣手环,一个留给自己,一个送给她。每个手环上各有 n 个装饰物,并且每个装饰物都有一定的亮度。
但是在她生日的前一天,我的室友突然发现他好像拿错了一个手环,而且已经没时间去更换它了!他只能使用一种特殊的方法,将其中一个手环中所有装饰物的亮度增加一个相同的自然数 c(即非负整数)。并且由于这个手环是一个圆,可以以任意的角度旋转它,但是由于上面装饰物的方向是固定的,所以手环不能翻转。需要在经过亮度改造和旋转之后,使得两个手环的差异值最小。
在将两个手环旋转且装饰物对齐了之后,从对齐的某个位置开始逆时针方向对装饰物编号1,2,…,n,其中 n 为每个手环的装饰物个数, 第 1 个手环的 i 号位置装饰物亮度为 xi,第 2 个手环的 i 号位置装饰物亮度为 yi,两个手环之间的差异值为(参见输入输出样例和样例解释):
\[\sum_{i=1}^{n} (x_i-y_i)^2\]麻烦你帮他计算一下,进行调整(亮度改造和旋转),使得两个手环之间的差异值最小,这个最小值是多少呢?
输入输出格式
输入格式
输入数据的第一行有两个数n, m,代表每条手环的装饰物的数量为n,每个装饰物的初始亮度小于等于m。
接下来两行,每行各有n个数,分别代表第一条手环和第二条手环上从某个位置开始逆时针方向上各装饰物的亮度。
输出格式
输出一个数,表示两个手环能产生的最小差异值。注意在将手环改造之后,装饰物的亮度
可以大于 m。
输入输出样例
输入样例#1
5 6
1 2 3 4 5
6 3 3 4 5
输出样例#1
1
说明
样例解释
需要将第一个手环的亮度增加1,第一个手环的亮度变为: 2 3 4 5 6
旋转一下第二个手环。对于该样例,是将第二个手环的亮度6 3 3 4 5向左循环移动一个位置,使得第二手环的最终的亮度为: 3 3 4 5 6。
此时两个手环的亮度差异值为1
数据范围
30%的数据满足n≤500, m≤10;
70%的数据满足n≤5000;
100%的数据满足1≤n≤50000, 1≤m≤100, 1≤ai≤m。
题意
给定序列x,y,可以使x序列旋转或者整体增减,求\(\min {\sum_{i=1}^n (x_i-y_i)^2}\)
分析
70分很好想到
先O(n^2)查找旋转的最优位置
然后O(n* m)查找最优的整体增减
那么复杂度瓶颈就在于查找旋转的最优位置
正解就开始推式子了
\[ans =\min (\sum_{i=1}^n (X_i+C-Y_{(i+p)\%n})^2)\]然后平方展开,得到
\[ans=\min (\sum_{i=1}^n -2X_iY_{(i+p)\%n} + \sum_{i=1}^n ((X_i+C)^2-2*C*Y_{(i+p)\%n}+Y_{(i+p)\% n}^2))\]对于前一个Σ求和,可以将x反转
\[\sum_{i=1}^n -2X_{n-i+1}Y_{(i+p)\%n}\]这个式子可以通过FFT求
后面的Σ求和,可以O(n* m)处理
code
70分暴力代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#define reg(x) for(int i=last[x];i;i=e[i].next)
#define inf 1e18
using namespace std;
inline ll read()
{
ll f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=1e5+6;
int n,m,x[maxn],y[maxn],pos;
ll sum,ans,Min=inf;
int main()
{
n=read(),m=read();
rep(i,1,n)x[i+n]=x[i]=read();
rep(i,1,n)y[i+n]=y[i]=read();
rep(i,1,n){
sum=0;
rep(j,i,i+n-1)sum+=(x[j]-y[j-i+1])*(x[j]-y[j-i+1]);
if(sum<Min)Min=sum,pos=i;
}
ans=Min;
rep(c,1,m){
sum=0;
rep(j,pos,pos+n-1)sum+=(x[j]+c-y[j-pos+1])*(x[j]+c-y[j-pos+1]);
ans=min(sum,ans);sum=0;
rep(j,pos,pos+n-1)sum+=(x[j]-y[j-pos+1]-c)*(x[j]-y[j-pos+1]-c);
ans=min(sum,ans);
}
cout<<ans<<endl;
return 0;
}
正解代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<complex>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define dep(i,a,b) for(ll i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#define reg(x) for(int i=last[x];i;i=e[i].next)
using namespace std;
inline ll read()
{
ll f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
typedef complex<double> E;
const int maxn=5e4+6;
double pi=acos(-1.0);
E A[maxn<<3],B[maxn<<3],c[maxn<<3];
double x[maxn<<3],y[maxn<<3];
ll ans1,ans2,sum1,sum2,ans,ans3;
int len,n,m,R[maxn<<3],lg;
void fft(E *a,int f){
rep(i,0,len-1)
if(i<R[i])swap(a[i],a[R[i]]);
for(int i=1;i<len;i<<=1){
E wn(cos(pi/i),f*sin(pi/i));
for(int j=0;j<len;j+=(i<<1)){
E w(1.0,0);
for(int k=0;k<i;k++,w*=wn){
E x=a[j+k],y=w*a[j+k+i];
a[j+k]=x+y;
a[j+k+i]=x-y;
}
}
}
}
int main()
{
n=read(),m=read();
rep(i,1,n){
scanf("%lf",&x[i]);
ans1+=x[i]*x[i];
sum1+=x[i];
}
rep(i,1,n){
scanf("%lf",&y[i]);
ans1+=y[i]*y[i];
sum2+=y[i];
y[i+n]=y[i];
}
rep(i,1,n)A[i]=x[n-i+1];
rep(i,1,2*n)B[i]=y[i];
len=1;while(len<4*n)len*=2,lg++;
rep(i,0,len)R[i]=(R[i>>1]>>1)|((i&1)<<(lg-1));
fft(A,1),fft(B,1);
rep(i,0,len-1)c[i]=A[i]*B[i];
fft(c,-1);
ans=1e9;
rep(i,-m,m){
ans2=ans1;
ans2+=2*i*sum1;ans2-=2*i*sum2;
ans2+=n*i*i;
rep(j,0,n-1){
ans3=ans2;
ans3-=2*(ll)(c[n+j+1].real()/len+0.5);
ans=min(ans,ans3);
}
}
printf("%lld\n",ans);
}