loj2020 「HNOI2017」礼物

所有的下标从 \(0\) 开始。

考虑枚举 \(C\) (第一个加上负的等于第二个加上其绝对值)和第二个手链的偏移量 \(p\)。答案就是
\[\sum_{i=0}^{n-1}(x_i+C-y_{(i+p) \bmod n})^2\]
复制一遍 \(y\) 数组就能去掉取模了,再展开就是
\[\sum_{i=0}^{n-1}((x_i+C)^2-2(x_i+C)y_{i+p}+y_{i+p}^2)\]
再展开就是
\[\sum_{i=0}^{n-1}(x_i+C)^2-2\sum_{i=0}^{n-1}x_iy_{i+p}-2C\sum_{i=0}^{n-1}y_i+\sum_{i=0}^{n-1}y_i^2\]

发现除了第二项别的都可以预处理后在枚举 \(C\)\(O(1)\) 得到,问题在于怎样快速求第二项。将 \(x\) 数组翻转就成了
\[\sum_{i=0}^{n-1}x_{n-i-1}y_{i+p}\]
显然 \(p\) 所对应的数就是 fft 后的第 \(n+p-1\) 项。fft 一次后枚举 \(C,p\) 即可。当然你也可以三分 \(C\)

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
int n, m, xx[50005], yy[50005], lim=1, tmpcnt, rev[300005];
ll ans=0x3f3f3f3f3f3f3f3f, sumxi, sumyi, sumxifang, sumyifang;
const double PI=acos(-1.0);
struct Complex{
    double x, y;
    Complex(double xx=0.0, double yy=0.0){
        x = xx;
        y = yy;
    }
    Complex operator+(const Complex &u)const{
        return Complex(x+u.x, y+u.y);
    }
    Complex operator-(const Complex &u)const{
        return Complex(x-u.x, y-u.y);
    }
    Complex operator*(const Complex &u)const{
        return Complex(x*u.x-y*u.y, x*u.y+y*u.x);
    }
}A[300005], B[300005];
void fft(Complex a[], int opt){
    for(int i=0; i<lim; i++)
        if(i<rev[i])
            swap(a[i], a[rev[i]]);
    for(int i=2; i<=lim; i<<=1){
        Complex wn=Complex(cos(PI*2/i), opt*sin(PI*2/i));
        int tmp=i>>1;
        for(int j=0; j<lim; j+=i){
            Complex w=Complex(1.0, 0.0);
            for(int k=0; k<tmp; k++){
                Complex tmp1=a[j+k], tmp2=w*a[j+k+tmp];
                a[j+k] = tmp1 + tmp2;
                a[j+k+tmp] = tmp1 - tmp2;
                w = w * wn;
            }
        }
    }
    if(opt<0)
        for(int i=0; i<lim; i++)
            a[i].x /= lim;
}
int main(){
    cin>>n>>m;
    for(int i=0; i<n; i++){
        scanf("%d", &xx[i]);
        sumxi += xx[i];
        sumxifang += xx[i] * xx[i];
        A[n-i-1].x = xx[i];
    }
    for(int i=0; i<n; i++){
        scanf("%d", &yy[i]);
        sumyi += yy[i];
        sumyifang += yy[i] * yy[i];
        B[i+n].x = B[i].x = yy[i];
    }
    while(lim<=3*n) lim <<= 1, tmpcnt++;
    for(int i=0; i<lim; i++)
        rev[i] = (rev[i>>1]>>1) | ((i&1)<<(tmpcnt-1));
    fft(A, 1);
    fft(B, 1);
    for(int i=0; i<lim; i++)
        A[i] = A[i] * B[i];
    fft(A, -1);
    for(int C=-m; C<=m; C++){
        for(int p=0; p<n; p++){
            ll tmp=(ll)(A[n+p-1].x+0.5);
            ll test=0;
            for(int i=0; i<n; i++)
                test += xx[i] * yy[(i+p)%n];
            tmp = -2 * tmp;
            tmp += sumxifang + (ll)2 * C * sumxi + (ll)C * C * n;
            tmp -= (ll)2 * C * sumyi;
            tmp += sumyifang;
            ans = min(ans, tmp);
        }
    }
    cout<<ans<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/poorpool/p/8821163.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值