P3723 [AH2017/HNOI2017]礼物(FFT)

P3723 [AH2017/HNOI2017]礼物(FFT)


题目传送门

[AH2017/HNOI2017]礼物

题目描述

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

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

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

∑ i = 1 n ( x i − y i ) 2 \sum_{i=1}^{n} (x_i-y_i)^2 i=1n(xiyi)2

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

输入格式

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

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

输出格式

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

样例 #1

样例输入 #1

5 6
1 2 3 4 5
6 3 3 4 5

样例输出 #1

1

提示

【样例解释】

需要将第一个手环的亮度增加 1 1 1,第一个手环的亮度变为: 2 , 3 , 4 , 5 , 6 2,3,4,5,6 2,3,4,5,6

旋转一下第二个手环。对于该样例,是将第二个手环的亮度 6 , 3 , 3 , 4 , 5 6,3,3,4,5 6,3,3,4,5 向左循环移动一个位置,使得第二手环的最终的亮度为: 3 , 3 , 4 , 5 , 6 3,3,4,5,6 3,3,4,5,6

此时两个手环的亮度差异值为 1 1 1

【数据范围】

对于 30 % 30\% 30% 的数据, n ≤ 500 n \le 500 n500 m ≤ 10 m \le 10 m10

对于 70 % 70\% 70% 的数据, n ≤ 5000 n \le 5000 n5000

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 50000 1 \le n \le 50000 1n50000, 1 ≤ a i ≤ m ≤ 100 1 \le a_i \le m \le 100 1aim100

思路

这是一个 F F T FFT FFT 转换题,不会的可以看这个

题意

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

分析

我们可以化简一下式子:
∑ i = 1 n ( a i − b i + c ) 2 = ∑ i = 1 n ( a i − b i ) 2 + c 2 + 2 c ( a i − b i ) = ∑ i = 1 n ( a i 2 + b i 2 ) − 2 a i b i + c 2 + 2 a i c − 2 b i c = ∑ i = 1 n a i 2 + ∑ i = 1 n b i 2 − 2 ∑ i = 1 n a i b i + n c 2 + 2 c ∑ i = 1 n ( a i − b i ) \begin{aligned} \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) \end{aligned} i=1n(aibi+c)2=i=1n(aibi)2+c2+2c(aibi)=i=1n(ai2+bi2)2aibi+c2+2aic2bic=i=1nai2+i=1nbi22i=1naibi+nc2+2ci=1n(aibi)
显然前两项是定值。

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

所以问题就转换成了求最大的 ∑ i = 1 n a i b i \sum_{i = 1}^na_ib_i i=1naibi

先破环为链,即 b i + n = b i b_{i + n}=b_i bi+n=bi ,就不用管环了

考虑把 b b b 移动 k k k 位时候的答案: ∑ i = 1 n a i b i + k \sum_{i = 1}^na_ib_{i+k} i=1naibi+k

翻转 a a a 数组后: ∑ i = 1 n a n − i + 1 b i + k \sum_{i = 1}^n a_{n - i + 1}b_{i + k} i=1nani+1bi+k

发现变成了一个卷积

就可以用 F F T FFT 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;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值