P3723 [AH2017/HNOI2017]礼物(FFT)

Posted 2020fengziyang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3723 [AH2017/HNOI2017]礼物(FFT)相关的知识,希望对你有一定的参考价值。

P3723 [AH2017/HNOI2017]礼物(FFT)

题目传送门

[AH2017/HNOI2017]礼物

题目描述

我的室友最近喜欢上了一个可爱的小女生。马上就要到她的生日了,他决定买一对情侣手环,一个留给自己,一个送给她。每个手环上各有 \\(n\\) 个装饰物,并且每个装饰物都有一定的亮度。

但是在她生日的前一天,我的室友突然发现他好像拿错了一个手环,而且已经没时间去更换它了!他只能使用一种特殊的方法,将其中一个手环中所有装饰物的亮度增加一个相同的非负整数 \\(c\\)。并且由于这个手环是一个圆,可以以任意的角度旋转它,但是由于上面装饰物的方向是固定的,所以手环不能翻转。需要在经过亮度改造和旋转之后,使得两个手环的差异值最小。

在将两个手环旋转且装饰物对齐了之后,从对齐的某个位置开始逆时针方向对装饰物编号 \\(1 \\sim n\\),其中 \\(n\\) 为每个手环的装饰物个数, 第 \\(1\\) 个手环的 \\(i\\) 号位置装饰物亮度为 \\(x_i\\),第 \\(2\\) 个手环的 \\(i\\) 号位置装饰物亮度为 \\(y_i\\),两个手环之间的差异值为(参见输入输出样例和样例解释):

\\[\\sum_i=1^n (x_i-y_i)^2 \\]

麻烦你帮他计算一下,进行调整(亮度改造和旋转),使得两个手环之间的差异值最小,这个最小值是多少呢?

输入格式

输入数据的第一行有两个数 \\(n,m\\),代表每条手环的装饰物的数量为 \\(n\\),每个装饰物的初始亮度小于等于 \\(m\\)

接下来两行,每行各有 \\(n\\) 个数,分别代表第一条手环和第二条手环上从某个位置开始逆时针方向上各装饰物的亮度。

输出格式

输出一个数,表示两个手环能产生的最小差异值。注意在将手环改造之后,装饰物的亮度可以大于 \\(m\\)

样例 #1

样例输入 #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 \\le 500\\)\\(m \\le 10\\)

对于 \\(70\\%\\) 的数据,\\(n \\le 5000\\)

对于 \\(100\\%\\) 的数据,\\(1 \\le n \\le 50000\\), \\(1 \\le a_i \\le m \\le 100\\)

思路

这是一个 \\(FFT\\) 转换题,不会的可以看这个

题意

给定两个数组 \\(a , b\\)\\(b\\) 可以循环移动,选择整数 \\(c\\in[-m , m]\\) , 求 \\(\\sum_i = 1^n(a_i-b_i+c)^2\\)最小值

分析

我们可以化简一下式子:

\\[\\beginaligned \\sum_i = 1^n(a_i - b_i +c)^2 &= \\sum_i = 1^n(a_i-b_i)^2+c^2+2c(a_i-b_i) \\newline &= \\sum_i = 1^n(a_i^2+b_i^2)-2a_ib_i+c^2+2a_ic-2b_ic \\newline &=\\sum_i = 1^na_i^2+\\sum_i = 1^n b_i^2-2\\sum_i = 1^na_ib_i+nc^2+2c\\sum_i = 1^n(a_i-b_i) \\endaligned \\]

显然前两项是定值。

后面的两项是关于 \\(c\\) 的二次函数,求对称轴,找到对称轴最近的两个整数坐标比较一下就好了。

所以问题就转换成了求最大的 \\(\\sum_i = 1^na_ib_i\\)

先破环为链,即 \\(b_i + n=b_i\\) ,就不用管环了

考虑把 \\(b\\) 移动 \\(k\\) 位时候的答案: \\(\\sum_i = 1^na_ib_i+k\\)

翻转 \\(a\\) 数组后:\\(\\sum_i = 1^n a_n - i + 1b_i + k\\)

发现变成了一个卷积

就可以用 \\(FFT\\) 了。

#include <bits/stdc++.h>
#define fu(x, y, z) for (int x = y; x <= z; x++)
#define fd(x, y, z) for (int x = y; x >= z; x--)
#define LL long long
using namespace std;
const int N = (1 << 18);
const double pi = acos(-1.0);
struct node 
    double x, y;
 A[N], B[N];
int n, m, a[N], b[N], n1, m1, n2;
node operator + (node a, node b)  return (node)a.x + b.x , a.y + b.y;
node operator - (node a, node b)  return (node)a.x - b.x , a.y - b.y;
node operator * (node a, node b)  return (node)a.x * b.x - a.y * b.y , a.x * b.y + a.y * b.x;
LL rev[N], sum[N], ans1, ans, t;
int read() 
    int val = 0, fu = 1;
    char ch = getchar();
    while (ch < \'0\' || ch > \'9\') 
        if (ch == \'-\')
            fu = -1;
        ch = getchar();
    
    while (ch >= \'0\' && ch <= \'9\') 
        val = val * 10 + (ch - \'0\');
        ch = getchar();
    
    return val * fu;

void fft(int len, node *a, int inv) 
    if (len == 1)
        return;
    node a1[(len >> 1) + 5], a2[(len >> 1) + 5];
    for (int i = 0; i <= len; i += 2) 
        a1[i >> 1] = a[i], a2[i >> 1] = a[i + 1];
    
    fft(len >> 1, a1, inv);
    fft(len >> 1, a2, inv);
    node wn = (node) cos(2.0 * pi / len), inv * sin(2.0 * pi / len) , w = (node) 1, 0 ;
    for (int i = 0; i < (len >> 1); i++, w = w * wn) 
        a[i] = a1[i] + w * a2[i];
        a[i + (len >> 1)] = a1[i] - w * a2[i];
    

void solve() 
    fu(i, 0, n1) A[i].x = a[i + 1];
    fu(i, 0, m1) B[i].x = b[i + 1];
    int len = 1;
    while (len <= n1 + m1) len <<= 1;
    fft(len, A, 1), fft(len, B, 1);
    fu(i, 0, len) A[i] = A[i] * B[i];
    fft(len, A, -1);
    fu(i, 0, m1 + n1 + 1) rev[i] = (LL)(A[i].x / len + 0.5);

int main() 
    n = read(), m = read();
    n1 = n - 1, n2 = (n << 1);
    m1 = n2 - 1;
    fu(i, 1, n) a[i] = read();
    fu(i, 1, n) b[i] = read();
    fu(i, 1, n) 
        t += b[i] - a[i];
        ans += a[i] * a[i] + b[i] * b[i];
        b[i + n] = b[i];
    
    int c1 = floor(t * 1.0 / n), c2 = ceil(t * 1.0 / n);
    ans += min(n * c1 * c1 - 2 * c1 * t, n * c2 * c2 - 2 * c2 * t);
    reverse(a + 1, a + n + 1);
    solve();
    fu (i , 0 , n) ans1 = max (ans1 , rev[i + n]);
    ans -= ans1 * 2;
    printf("%lld", ans);
    return 0;

[AH/HNOI2017]礼物

\[推推公式,即求\Sigma^{n}_{i=1} (x_{i+k}-y_i+c)^2最小,c范围为[-m, m]\]
\[拆开,就是\Sigma x_i^2 + \Sigma y_i^2 + n * c^2 + 2*c*\Sigma(x_{i+k}-y_i) - 2*\Sigma^{n}_{i=1} x_{i+k}y_i\]
\[即求2*\Sigma^{n}_{i=1} x_{i+k}y_i最大,再枚举c即可\]

七十分暴力代码(暴力分贼多)

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(1e5 + 10);

IL ll Read(){
    char c = '%'; ll x = 0, z = 1;
    for(; c > '9' || c < '0'; c = getchar()) if(c == '-') z = -1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
    return x * z;
}

int n, m;
ll sqx, sqy, sx, sy, x[_], y[_], ans = -1e18, mn = 1e18;

int main(RG int argc, RG char *argv[]){
    n = Read(); m = Read();
    for(RG int i = 1; i <= n; ++i) x[i + n] = x[i] = Read(), sx += x[i], sqx += x[i] * x[i];
    for(RG int i = 1; i <= n; ++i) y[i] = Read(), sy += y[i], sqy += y[i] * y[i];
    for(RG int i = 0; i < n; ++i){
        RG ll cnt = 0;
        for(RG int j = 1; j <= n; ++j) cnt += x[j + i] * y[j];
        ans = max(ans, cnt);
    }
    for(RG int c = -m; c <= m; ++c) mn = min(mn, 1LL * n * c * c + 1LL * 2 * c * (sx - sy) - 2 * ans);
    printf("%lld\n", mn + sqx + sqy);
    return 0;
}

\[\Sigma^{n}_{i=1} x_{i+k}y_i,很套路,就往FFT上靠,把y反转不就变成\Sigma^{n}_{i=1} x_{i+k}y_{n-i+1}\]
\[这不就是卷积,就是多项式相乘后第n+k+1项的系数,这就可以FFT了\]


把y反转,再倍长,跑一遍FFT,取有用的中间一段的最大值
再枚举c求解即可


# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(4e5 + 10);
const double Pi(acos(-1));

IL ll Read(){
    char c = '%'; ll x = 0, z = 1;
    for(; c > '9' || c < '0'; c = getchar()) if(c == '-') z = -1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
    return x * z;
}

struct Complex{
    double real, image;
    IL Complex(){  real = image = 0;  }
    IL Complex(RG double a, RG double b){  real = a; image = b;  }
    IL Complex operator +(RG Complex B){  return Complex(real + B.real, image + B.image);  }
    IL Complex operator -(RG Complex B){  return Complex(real - B.real, image - B.image);  }
    IL Complex operator *(RG Complex B){  return Complex(real * B.real - image * B.image, real * B.image + image * B.real);  }
} A[_], B[_];
int n, m, N, M, l, r[_];
ll sx, sy, sqx, sqy, mx = -1e18, ans = 1e18;


IL void FFT(RG Complex *P, RG int opt){
    for(RG int i = 0; i < N; i++) if(i < r[i]) swap(P[i], P[r[i]]);
    for(RG int i = 1; i < N; i <<= 1){
        RG Complex W(cos(Pi / i), opt * sin(Pi / i));
        for(RG int p = i << 1, j = 0; j < N; j += p){
            RG Complex w(1, 0);
            for(RG int k = 0; k < i; ++k, w = w * W){
                RG Complex X = P[k + j], Y = w * P[k + j + i];
                P[k + j] = X + Y; P[k + j + i] = X - Y;
            }
        }
    }
}

int main(RG int argc, RG char *argv[]){
    n = Read() - 1; m = Read();
    for(RG int i = 0; i <= n; ++i) A[i].real = Read(), sx += A[i].real, sqx += A[i].real * A[i].real;
    for(RG int i = n; i >= 0; --i) B[i + n + 1].real = B[i].real = Read(), sy += B[i].real, sqy += B[i].real * B[i].real;
    for(M = 3 * n, N = 1; N <= M; N <<= 1) ++l;
    for(RG int i = 0; i < N; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    FFT(A, 1); FFT(B, 1);
    for(RG int i = 0; i < N; ++i) A[i] = A[i] * B[i];
    FFT(A, -1);
    for(RG int i = n; i <= 2 * n; ++i) mx = max(mx, (ll)(A[i].real / N + 0.5));
    for(RG int c = -m; c <= m; ++c) ans = min(ans, 1LL * (n + 1) * c * c + 1LL * 2 * c * (sx - sy));
    printf("%lld\n", ans + sqx + sqy - 2 * mx);
    return 0;
}

以上是关于P3723 [AH2017/HNOI2017]礼物(FFT)的主要内容,如果未能解决你的问题,请参考以下文章

luogu3723 [AH2017/HNOI2017]礼物 NTT

[AH2017/HNOI2017]单旋

#10 [AH2017/HNOI2017]大佬

[AHOI2017/HNOI2017][bzoj4827] 礼物 [FFT]

FFT/NTT/FMT/FWT题目

FFT/NTT/FMT/FWT题目