[HNOI2017]礼物 解题报告(数学常识+FFT)

[HNOI2017]礼物

https://ac.nowcoder.com/acm/problem/20122

估计多项式会吓到不少人,写个题解装个b。
我们数学题嘛,我们把原式写出来:
题目要求的就是:
设x序列都加u,y序列都加v,则


我们可以令u-v=t,则:


运用初中数学知识,我们我们可以把上式子看作一个二次函数,变量为t。
那么,我们就可以知道当u-v取多少的时候,相同x,y下,res能够最小。因此,亮度调整并不是本题的难点所在。
但是,题目还允许我们使得x对应的y不一样。观察上式子,我们发现:只有是同时关联x和y的,我们需要求出的是这个式子的最小值。
我们可以把这个式子变一下:,这里的x是原来的x反转之后的。我们发现,这是个一个卷积的形式。那么,对于卷积,我们自然想到多项式算法(FFT,NTT),因为没有取模,所以考虑FFT。
如果对多项式熟悉的小伙伴看到这里就可以码起来了,但像我这样的菜鸡,还不熟悉多项式和卷积,还需要看下具体过程。
我们假设两个多项式,为了方便说明,我们取具体数值:

则:
所以,我们可以得到结论:n次多项式a和m次多项式b(n>=m)相乘之后为:


也可以看作:

这里如果不明确,建议手算下。
因此,我们可以用多项式乘法解决问题。
其实,这里其实就是卷积的形式。这也是卷积和多项式乘法之间的关系桥梁。
那么,得到这个结论之后要怎么用呢?
我们令b[i+n]=b[i],也就是b的长度变为2n
此时,我们再那a,b作为多项式进行相乘:(这里举个n=2的例子,可能不够味)
结果为


其中,
我们可以非常清楚的看到,我们所需要的a[i]b[n-i]这个东西其实就是多项式相乘之后的第n到2*n-1项的系数,我们直接遍历求max就行了。
总结下:重点是要理解好卷积和多项式乘积的关系,有了这个,加上数学变形,其实就能写出这道题了。
复杂度应该是(nlogn)?但是常数挺大的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>
void read(T&x){
    ll f=1;
    x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    x*=f;
}
//================================================
#define int ll
const int maxn=3e5+100;
#define complex Complex
struct Complex{
    double x,y;
    complex(double xx=0,double yy=0){
        x=xx;
        y=yy;
    }
    friend complex operator+(const complex&a,const complex&b){return complex(a.x+b.x,a.y+b.y);}
    friend complex operator-(const complex&a,const complex&b){return complex(a.x-b.x,a.y-b.y);}
    friend complex operator*(const complex&a,const complex&b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);}
}numa[maxn],numb[maxn];
int a[maxn],b[maxn];
const double PI=acos(-1);
int lim=1,r[maxn];
int n,m;
int l;
void fft(Complex*a,int lim,int type){
    for(int i=0;i<=lim;i++)if(i<r[i])swap(a[i],a[r[i]]);
    for(ll mid=1;mid<lim;mid<<=1){
        complex Wn(cos(PI/mid),type*sin(PI/mid));
        int R=(mid<<1);
        for(ll j=0;j<lim;j+=R){
            complex w(1,0);
            for(ll k=0;k<mid;k++,w=w*Wn){
                complex x=a[j+k],y=w*a[j+mid+k];
                a[j+k]=x+y;
                a[j+mid+k]=x-y;
            }
        }
    }
}

int cal(int a,int b,int x){
    return a*x*x+b*x;
}
#define inf 0x3f3f3f3f
int get_min(){
    int res=0;
    int ab=0;
    for(int i=0;i<n;i++){
        res+=a[i]*a[i];
        res+=b[i]*b[i];
        ab+=a[i]-b[i];
    }
    int px=-ab/n;
    int P2=inf;
    int L=floor(-1.0*ab/n),R=ceil(-1.0*ab/n);
    for(int i=L;i<=R;i++){
        P2=min(P2,n*i*i+2*i*ab);
    }
    return res+P2;
}
ll res[maxn];
int solve(){
    int p1=get_min();
    while(lim<=(n*3))lim<<=1,++l;
    for(int i=0;i<lim;++i)r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
    for(int i=0;i<n;++i)numb[i+n].x=numb[i].x;
    reverse(numa,numa+n);
    fft(numa,lim,1);fft(numb,lim,1);
    for(int i=0;i<lim;++i)numa[i]=numa[i]*numb[i];
    fft(numa,lim,-1);
    for(int i=0;i<lim;++i){
        res[i]=(numa[i].x/lim+0.5);
    }
    ll tmp=0;
    for(int i=0;i<n;i++){
        tmp=max(tmp,res[i+n]);
    }
    return p1-(tmp<<1);
}

signed main(){
    //freopen("in.txt","r",stdin);
    read(n),read(m);
    for(int i=0;i<n;i++){
        int x;read(x);
        a[i]=x;
        numa[i].x=x;
    }
    for(int i=0;i<n;++i){
        int x;read(x);
        b[i]=x;
        numb[i].x=x;
    }
    printf("%lld\n",solve());
    return 0;
}
全部评论

相关推荐

12-03 21:23
武汉大学 Java
点赞 评论 收藏
分享
从小父母离异家里没人管,靠着心里的不安和学校的环境也算是坚持到了学有所成的地步。到了大学环境开始松散不知道该做什么,只觉得在不挂科的基础上能往上考多少就考多少,等到秋招来临才发现自己有多么幼稚无能,今年九月份初才发现自己原来连一个求职的方向都没有。因为之前做过前后端一体的课设,算是有过了解,而对于其他岗位连做什么都不知道,因此这一个半个月在越来越焦虑的同时埋头苦学,事到如今想要活下去我似乎只能走前端这条路了,9月初先是靠着虚假夸大能力的简历得到一些笔试来确定了考察的方向,有一个大厂的无笔试面试最终是拒绝了没有勇气去面对。然后在这个基础上埋头苦学,如今也算是搭好了自己前端学习的框架和思考的瞄,可以逐渐给自己扩展新的知识和能力了,但这并不是一件多好的事儿,因为我发现学的越多越焦虑,学的越多便越无力。因为我感觉我如今努力学习的知识都是竞争对手们早就掌握了的东西,我如今困惑追求答案的难题早就被别人解决。别人早就能得心应手地做出项目而我连思考都会卡壳,看着别人的笔试和面经上那些闻所未闻的题目,我才知道别人到底有多强而我有多幼稚,我什么时候才能达到别人那种堪称熟练的能力呢?而且网上的焦虑越多越多,即便是真有这么高的能力最后也大概落得一个低薪打工人的下场,我真的感到迷茫。秋招都快结束了,而我还在继续痛苦的学习之旅,这些天找前端面试发现似乎问的有些简单跟网上搜到的内容不符(可能因为并不是大厂),我是不是本来就没打算被招所以别人懒得细问呢?我不知道,我只能继续总结下去学习下去,不管如何我都要活下去,如果我能早一些准备就好了,如果暑假能意识到现在这个情况就好了,可惜没有如果。种下一棵树的最好时间是十年前,其次是现在,虽然我相信自己的学习能力,但已经错过了最好的时机,只能在焦虑与痛苦中每天坚持学下去。目前的路还有很长很长,先去把typescript看了,再去巩固vue3的基础,再去练习elementui的使用,如果这能找到实习的话就好了。接下来呢?去学uniapp和小程序,不管如何我都要对得起曾经努力的自己。即便我们都感到痛苦,但我心中还是希望我们都能靠自己的努力来获取自己想要的幸福。
紧张的牛牛等一个of...:在担心什么呢,有一手985的学历在,就算是小厂别人都会要的,咱们双非的人更多,多少还在沉沦的,怕什么了
一句话证明你在找工作
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务