标签: NTT
题意
有两个手链, 这两个手链上分别有 \(n\) 个装饰品, ($ 1 \le n \le 5 \times 10^4$).
每个装饰品都有一个亮度, 两个手链上装饰品的亮度分别为 \(a_i,\ b_i\), 且 \(1 \le a_i,\ b_i \le m\), (\(1 \le m \le 100\)).
现可以将一个手链上的所有装饰品的亮度值增加一个非负正数 \(c\), 并可以将手链进行旋转, 使得
\[ \sum_{i=1}^{n} (a_i-b_i)^2 \]
最小, 并求出这个最小值
思路
为了描述方便, 我们把 $ \sum_{i=1}^{n} (a_i-b_i)^2 $ 这个式子称作 "亮度差".
现考虑把一个手链的亮度值增加 \(c\) 后, 亮度差会如何变化,
前后亮度差的差值为
\[ \begin{aligned} \sum_{i=1}^{n} [(a_i+c)-b_i]^2 - \sum_{i=1}^{n} (a_i-b_i)^2 &= \sum_{i=1}^{n} c^2 + \sum_{i=1}^{n} 2a_ic - \sum_{i=1}^{n} 2b_ic \\ &= nc^2 + 2c\sum_{i=1}^{n} a_i - b_i \\ \end{aligned} \]
惊奇地发现, 无论装饰品的对应关系如何, 即不管手链如何旋转, 亮度差的变化值是一定的, 并且是关于亮度增加值 \(c\) 的一个二次函数, 可以直接求得最小值.
这样的话, 我们就只需要考虑如何旋转手链能使得初始亮度差最小.
初始亮度差为,
\[ \sum_{i=1}^{n} (a_i - b_i)^2 = \sum_{i=1}^{n} a_i^2 + b_i^2 - 2\sum_{i=1}^{n} a_ib_i \]
要是上述式子最小, 就需要求到 \(\sum_{i=1}^{n} a_ib_i\) 的最大值.
枚举两个对应装饰品之间的距离 \(k\), 则 \(h[k]=\sum_{i=1}^{n} a_ib_{(i+k) \% m}\). ( 设 \(b_0 = b_n\) )
为了化成加法卷积的形式, 设 \(a_{-i}=a_i\) 则\(h[k] = \sum_{i=1}^{n} a_{-i}b_{(i+k) \%m}\),
然后就可以愉快地 \(NTT\) 了, 还要记得把 \(a_i\) 往前推 \(n\) 位, 并倍长 \(b\) 或 \(a\) ( 说实话我也不太清楚为什么这样可以, 但它就是可以... ).
代码
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int _=3e5+7;
const int p=998244353;
const int rt=3;
bool be;
int n,m,a[_],b[_],f[_],g[_],t,invt,invrt,num[_];
bool en;
int q_pow(int a,int k){
int res=1;
while(k){
if(k&1) res=(ll)res*a%p;
a=(ll)a*a%p; k>>=1;
}
return res;
}
void NTT(int *f,int id){
for(int i=0;i<t;i++)
if(i<num[i]) swap(f[i],f[num[i]]);
for(int len=2;len<=t;len<<=1){
int gap=len>>1;
int w1=q_pow(id==1 ?rt :invrt,(p-1)/len);
for(int k=0;k<t;k+=len){
int w=1;
for(int i=k;i<k+gap;i++,w=(ll)w*w1%p){
int tmp=(ll)w*f[i+gap]%p;
f[i+gap]=(f[i]-tmp+p)%p;
f[i]=(f[i]+tmp)%p;
}
}
}
}
int main(){
//freopen("gift.in","r",stdin);
//freopen("gift.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++){ scanf("%d",&a[i]); a[0]+=a[i]; }
for(int i=1;i<=n;i++){ scanf("%d",&b[i]); b[0]+=b[i]; }
int A=-n,B=2*(a[0]-b[0]);
db tmp=(db)-B/(2*A);
db t1=tmp-floor(tmp),t2=ceil(tmp)-tmp;
int c= t1<t2 ?floor(tmp) :ceil(tmp);
//printf("c: %d\n",c);
c=A*c*c+B*c;
for(int i=0;i<n;i++) f[i]=a[n-i];
for(int i=1;i<=n;i++) g[i]=b[i];
for(int i=n+1;i<=2*n;i++) g[i]=b[i-n];
t=1; while(t<=2*n) t<<=1;
invt=q_pow(t,p-2); invrt=q_pow(rt,p-2);
for(int i=0;i<t;i++)
num[i]=(num[i>>1]>>1)|((i&1) ?t>>1 :0);
NTT(f,1);
NTT(g,1);
for(int i=0;i<t;i++) f[i]=(ll)f[i]*g[i]%p;
NTT(f,-1);
ll ans=-0x3f3f3f3f,res=0;
for(int i=n+1;i<=2*n;i++) ans=max(ans,(ll)f[i]*invt%p);
for(int i=1;i<=n;i++) res+=a[i]*a[i]+b[i]*b[i];
ans=res-2*ans-c;
printf("%lld\n",ans);
// printf("\nused space: %.2lfMB\n",(&en-&be)/1048576.0);
return 0;
}