洛谷 P3723 [AH2017/HNOI2017]礼物 fft

题目描述

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

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

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

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

输入输出格式

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

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

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

可以大于 m

输入输出样例

输入样例#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 500 , m 10

70%的数据满足 n 5000

100%的数据满足 1 n 50000 , 1 m 100 , 1 a i m

分析:
因为每次都是给所有位置加,肯定不是dp啦。
设修改后的序列为 ( a 0 , a 2 , . . . , a n 1 ) ( b 1 , b 2 , . . . , b n 1 ) ,则

a n s = i = 0 n 1 ( a i b i + c ) 2

= i = 0 n 1 ( a i 2 + b i 2 2 a i b i + c 2 + 2 a i c 2 b i c )

后面的就是一个二次函数,而只有 a i b i 的项一开始就确定了,我们考虑使 i = 0 n 1 a i b i 最大。
因为我们可以移动位置,即求
m a x ( i = 0 n 1 a i b ( i + k )   m o d   n )

我们设 c i = a n i 1 ,也就是翻转 a 序列,然后使 b n + i = b i ,那么
m a x ( i = 0 n 1 c n i 1 b i + k )

这个显然就是一个卷积形式,弄出每一项然后取最大值即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=3e5+7;
const double pi=acos(-1);

using namespace std;

int n,len,m;
int r[maxn];
LL f[maxn],g[maxn];
LL ans,b;

struct rec{
    double x,y;
}x[maxn],y[maxn],z[maxn],w[maxn];

rec operator +(rec a,rec b)
{
    return (rec){a.x+b.x,a.y+b.y};
}

rec operator -(rec a,rec b)
{
    return (rec){a.x-b.x,a.y-b.y};
}

rec operator *(rec a,rec b)
{
    return (rec){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};
}

void fft(rec *a,int f)
{
    for (int i=0;i<len;i++)
    {
        if (i<r[i]) swap(a[i],a[r[i]]);
    }
    w[0]=(rec){1,0};
    for (int i=2;i<=len;i*=2)
    {
        rec wn=(rec){cos(2*pi/i),f*sin(2*pi/i)};
        for (int j=i/2;j>=0;j-=2) w[j]=w[j/2];
        for (int j=1;j<i/2;j+=2) w[j]=w[j-1]*wn;
        for (int j=0;j<len;j+=i)
        {
            for (int k=0;k<i/2;k++)
            {
                rec u=a[j+k],v=a[j+k+i/2]*w[k];
                a[j+k]=u+v;
                a[j+k+i/2]=u-v;
            }
        }
    }
}

void FFT(LL *a,LL *b,LL *c,int n,int m)
{
    len=1;
    while (len<=(n+m)) len*=2;
    int k=trunc(log(len+0.5)/log(2));
    for (int i=0;i<len;i++)
    {
        x[i].x=f[i];
        y[i].x=g[i];
        r[i]=(r[i>>1]>>1)|((i&1)<<(k-1));
    }
    fft(x,1); fft(y,1);
    for (int i=0;i<len;i++) z[i]=x[i]*y[i];
    fft(z,-1);
    for (int i=0;i<len;i++) c[i]=trunc((z[i].x+0.5)/len);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=0;i<n;i++) scanf("%lld",&f[i]);
    for (int i=0;i<n;i++) scanf("%lld",&g[i]);  
    for (int i=0;i<n;i++)
    {
        ans+=f[i]*f[i]+g[i]*g[i];
        b+=f[i]-g[i];  
    }       
    double delta=(double)b/(double)(-n);
    LL x=round(delta);
    ans+=n*x*x+2*b*x;
    for (int i=0;i<n;i++)
    {
        if (i<n-i-1) swap(f[i],f[n-i-1]);
        g[i+n]=g[i];
    }   
    FFT(f,g,f,n,2*n);
    LL k=-0x3f3f3f3f;
    for (int i=0;i<3*n;i++) k=max(k,f[i]);
    printf("%lld",ans-2*k);
}

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/81632055