【HNOI2017】礼物-gift

题面

  

解法

FFT:
  和式可化为:

i=1n(xi+cyi)2=i=1n(x2i+y2i)+nc22ci=1n(yixi)2i=1n(xiyi)

  当c确定时,前三项都为定值,为了使和式最小,只需要使得在旋转之后 xiyi 的和最大,即求旋转后的 ni=1(xiyi)max
  * : c∈[-m,m]
  ** : 求 ni=1(xiyi)max 只需利用FFT。假设旋转第一个环之后第一个环的第1个是原来第一个环的第k个,那么此时的 ni=1(xiyi) 就为:
i=1nk+1(xk+i1yi)+i=1k1(xiynk+i+1)······

  如果把第一个环翻转,把环看作多项式,那么①式就是两个多项式乘积中一个系数的值,所以我们可以只翻转第一个环做一次FFT,然后只翻转第二个环做一次FFT,取两次结果中最大的系数就可以了

复杂度

O( nlogn+m

代码

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#define Rint register int
#define Lint long long int
using namespace std;
const double pi=acos(-1);
const int INF=0x3f3f3f3f;
const int N=150010;
struct complex  
{  
    double r,i;
    complex operator + (const complex o)
    {  
        return (complex){ r+o.r,i+o.i };
    }
    complex operator - (const complex o)
    {  
        return (complex){ r-o.r,i-o.i };
    }
    complex operator * (const complex o)
    {  
        return (complex){ r*o.r-i*o.i,r*o.i+i*o.r };
    }
}f[N],g[N],s[N];
int a[N],b[N],w[N],v[N];
int n,m,ans,mx,N1;
void FFT(complex a[N],int n,int m,int opt)
{
    for(int i=1,j=0;i<n;i++)
    {
        for(int k=n;j^=k>>=1,~j&k;)   ;
        if( i<j )   swap( a[i],a[j] );
    }
    complex wn,w,tmp;
    for(int d=0;(1<<d)<n;d++)
    {
        int m=1<<d;
        wn=(complex){ cos(pi/m),sin(opt*pi/m) };
        for(int i=0;i<n;i+=m*2)
        {
            w=(complex){ 1,0 };
            for(int j=0;j<m;j++,w=w*wn)
            {
                tmp=w*a[i+j+m];
                a[i+j+m]=a[i+j]-tmp,a[i+j]=a[i+j]+tmp;
            }
        }
    }
}
void solve()
{
    if( !N1 )   for(N1=1;N1<=2*n;N1=N1*2)   ;
    FFT( f,N1,n,1 ),FFT( g,N1,n,1 );
    for(int i=0;i<=N1;i++)   s[i]=f[i]*g[i];
    FFT( s,N1,n,-1 );
    for(int i=0;i<=N1;i++)   f[i]=(complex){ 0,0 },g[i]=(complex){ 0,0 };
}
int main()
{
    freopen("gift.in","r",stdin);
    freopen("gift.out","w",stdout);
    int sum1,sum2,x;
    scanf("%d%d",&n,&m);
    sum1=sum2=0,ans=INF;
    for(int i=1;i<=n;i++)   scanf("%d",&a[i]),sum1+=a[i]*a[i];
    for(int i=1;i<=n;i++)   scanf("%d",&b[i]),sum1+=b[i]*b[i];
    for(int i=0;i<=n-1;i++)   f[i]=(complex){ a[n-i]*1.0,0 },g[i]=(complex){ b[i+1]*1.0,0 };
    solve();
    for(int i=1;i<=n;i++)   w[i]=(int)(s[n-i].r/N1+0.5);
    for(int i=0;i<=n-1;i++)   f[i]=(complex){ a[i+1]*1.0,0 },g[i]=(complex){ b[n-i]*1.0,0 };
    solve();
    for(int i=1;i<=n;i++)   v[i]=(int)(s[i-1].r/N1+0.5);
    for(int i=1;i<=n;i++)
        if( v[i-1]+w[i]>mx )   mx=v[i-1]+w[i],x=i;
    if( x>1 )//需要进行旋转
    {
        int tmp[N];
        for(int i=x;i<=n;i++)   tmp[i]=a[i];
        for(int i=n;i>=n-x+2;i--)   a[i]=a[i-n+x-1];
        for(int i=x;i<=n;i++)   a[i-x+1]=tmp[i];
    }
    for(int i=1;i<=n;i++)   sum2+=a[i]-b[i];
    if( sum2<0 )   sum2*=-1;
    for(int c=0;c<=m;c++)   ans=min( ans,sum1+n*c*c-2*c*sum2-2*mx );
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值