P3723 [AH2017/HNOI2017]礼物(FFT)

P3723 [AH2017/HNOI2017]礼物(FFT)

题目传送门

[AH2017/HNOI2017]礼物

题目描述

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

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

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

i=1n(xiyi)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% 的数据,n500m10

对于 70% 的数据,n5000

对于 100% 的数据,1n50000, 1aim100

思路

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

题意

给定两个数组 a,bb 可以循环移动,选择整数 c[m,m] , 求 i=1n(aibi+c)2最小值

分析

我们可以化简一下式子:

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 的二次函数,求对称轴,找到对称轴最近的两个整数坐标比较一下就好了。

所以问题就转换成了求最大的 i=1naibi

先破环为链,即 bi+n=bi ,就不用管环了

考虑把 b 移动 k 位时候的答案: i=1naibi+k

翻转 a 数组后:i=1nani+1bi+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;
}
posted @   2020fengziyang  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示