[AH2017/HNOI2017]礼物
题目
题解
我们称手链串为 \(a,b\) 两个.
假设最终我们对于其中某一个手镯增加 \(c(c\in R)\) 的光亮度,将后者旋转 \(k\) 位,首先,我们可以经典地破环为链,这里将 \(b\) 重复了一遍,那么最后的答案就是
\[Ans=\sum_{i=0}^{n-1}\left[a_i-(b_i+c)\right]^2
\]
我们可以将其打开,得到
\[\begin{aligned}
Ans&=\sum_{i=0}^{n-1}\left[a_i-(b_{i+k}+c)\right]^2 \\
&=\sum_{i=0}^{n-1}a_i^2+\sum_{i=0}^{n-1}(b_i+c)^2-\sum_{i=0}^{n-1}2a_i^2(b_{i+k}+c) \\
&=\sum_{i=0}^{n-1}a_i^2+\sum_{i=0}^{n-1}b_i^2+nc^2+2c\sum_{i=0}^{n-1}(a_i-b_{i+k})-2\sum_{i=0}^{n-1}a_ib_{i+k}
\end{aligned}
\]
发现关于 \(c\) 的项都是定值,二函经典求最值,只有 \(-2\sum_{i=0}^{n-1}a_ib_{i+k}\) 这个东东是关于 \(k\) 的,但是这个东西难道不熟悉?我们将 \(b\) 反转,那么这个东西就变成 \(-2\sum_{i=0}^{n-1}a_ib_{2n-i-k}\),这不就是卷积吗?它最后被卷到 \(2n-k\) 这一位了,所以我们只需要将 \(b\) 反转,将 \(a,b\) 卷起来,然后枚举 \(k\) 找 \((a*b_{\text{rev}})_{2n-k}\) 的最大值即可.
注意不要忘记了系数 \(2\).
代码
const int maxn=1e5;
const double Pi=acos(-1.0);
struct cplx{
double x,y;
inline cplx(const double X=0,const double Y=0):x(X),y(Y){}
inline cplx operator +(const cplx rhs)const{return cplx(x+rhs.x,y+rhs.y);}
inline cplx operator -(const cplx rhs)const{return cplx(x-rhs.x,y-rhs.y);}
inline cplx operator *(const cplx rhs)const{return cplx(x*rhs.x-y*rhs.y,x*rhs.y+y*rhs.x);}
inline cplx operator +=(const cplx rhs){return (*this)=(*this)+rhs;}
inline cplx operator -=(const cplx rhs){return (*this)=(*this)-rhs;}
inline cplx operator *=(const cplx rhs){return (*this)=(*this)*rhs;}
};
void fft(cplx* f,const int len,const short opt=1){
if(!len)return;
cplx f0[len+5],f1[len+5];
for(int i=0;i<len;++i)
f0[i]=f[i<<1],f1[i]=f[i<<1|1];
fft(f0,len>>1,opt);fft(f1,len>>1,opt);
cplx w=cplx(cos(Pi/len),opt*sin(Pi/len));
cplx buf=cplx(1,0);
for(int i=0;i<len;++i,buf*=w){
f[i]=f0[i]+buf*f1[i];
f[i+len]=f0[i]-buf*f1[i];
}
}
int n,m;
int a[maxn*2+5],b[maxn*2+5];
int sum;
inline void input(){
n=readin(1),m=readin(1);
for(int i=0;i<n;++i)sum+=(a[i]=readin(1));
for(int i=0;i<n;++i)sum-=(b[n-i-1]=readin(1));
}
cplx f[maxn*4+5],g[maxn*4+5];
int val[maxn*4+5];
inline void getval(){
int N=n*3;
for(int i=0;i<n;++i)
f[i]=cplx(a[i],0),g[i]=cplx(b[i],0);
for(int i=n;i<(n<<1);++i)g[i]=cplx(b[i-n],0);
int sz;for(sz=1;sz<N;sz<<=1);
fft(f,sz>>1);fft(g,sz>>1);
for(int i=0;i<sz;++i)f[i]*=g[i];
fft(f,sz>>1,-1);
for(int i=0;i<sz;++i)val[i]=(int)(f[i].x/sz+0.5);
}
inline int calc(const int c){
return n*c*c+2*sum*c;
}
inline void getans(){
int x=-sum/n;
// printf("x == %d\n",x);
int ans=Min(Min(calc(x-1),calc(x)),calc(x+1));
// printf("init :> ans == %d\n",ans);
for(int i=0;i<n;++i)
ans+=a[i]*a[i]+b[i]*b[i];
// printf("after pow, ans == %d\n",ans);
int del=0;
// rep(i,0,(n<<1))printf("%d ",val[i]);Endl;
// for(int i=n;i<(n<<1);++i)del=Max(del,val[i]);
for(int k=1;k<=n;++k)del=Max(del,val[(n<<1)-k]);
printf("%d\n",ans-2*del);
}
signed main(){
input();
getval();
getans();
return 0;
}
用到の小 \(\tt trick\)
第一,我们需要推柿子......
第二,识别出 \(-2\sum_{i=0}^{n-1}a_ib_{i+k}\) 其实是卷积的一种隐藏形式,将其中一个反转之后就是卷积的基本形式.
其实也可以说任何 \(\sum a_ib_i\) 其实都可以转换成卷积形式,只需要将其中一个数组反转即可.