zoukankan      html  css  js  c++  java
  • 斜率DP十连测


    最近学校里很多题目都没时间做,顺便来写一下博客
    斜率DP十连:

    A[征途]

    注意到,原题的式子,等价于mai2(ai)2后面部分是常数
    那么我们就可以写出dp方程f[i,j]=min{f[i1,k]+(sjsk)2},k<j
    对每个i单独做斜率dp,那么式子改写为fi=min{2sjsi+fj+sj2+si2}
    斜率为si,维护下凸包即可

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define LL long long 
    using namespace std;
    int n,m,q[3010],T; LL f[2][3010],s[3010];
    inline LL y(int j){
        return f[T^1][j]+s[j]*s[j];
    }
    inline db slp(int j,int k){
        return (y(j)-y(k))/(db)(2.*(s[j]-s[k])); 
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) scanf("%lld",s+i),s[i]+=s[i-1];
        for(int i=1;i<=m;++i){
            int l=0,r=0; T=i&1;
            for(int j=1;j<=n;++j){
                while(l<r && slp(q[l],q[l+1])<s[j]) ++l; int k=q[l];
                f[T][j]=y(k)+s[j]*s[j]-2*s[j]*s[k]; 
                while(l<r && slp(q[r-1],q[r])>slp(q[r],j)) --r;
                q[++r]=j;
            }
        }
        printf("%lld
    ",m*f[m&1][n]-s[n]*s[n]);
    }




    B[货币兑换]

    是一个神仙题也
    首先注意到一个贪心策略,每天要么全部买入,全部卖出或者什么也不做
    好的,注意到这一点之后就可以考虑怎么dp了,设fi表示到了第i天最多有多少钱
    xi表示到了第i天最多有多少a股,yi表示同时最多有多少b股
    我们有三种转移
    1.今天什么也不做fi=fi1
    2.今天卖掉从某一天买来的股票fi=max{aixj+biyj},j<i
    3.买入股票yi=fiairatei+bi,xi=yiratei
    重点关注第二条式子,我们将其改写:

    yj=aibixj+fibi

    这里b是常数可以不管,只需要维护上凸包,让后在凸包上二分斜率aibi即可
    但是发现aibi不是单调的,我们需要选择以下一项:
    1.splay维护凸包
    2.cdq分治
    前者明显比后者直观易懂,所以我们用splay维护凸包,过程和用队列差不多,只是如果一个点被加入到了凸包内部需要直接删除,而加入的有用的点,直接找它左边和右边第一个满足上凸的点,并将中间的都删掉即可

    #include<math.h>
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define N 100010
    #define db double
    #define eps 1e-9
    #define inf 1e9
    #define son(x) (s[f[x]][1]==x)
    using namespace std;
    int n,s[N][2],v[N],f[N],rt;
    db l[N],r[N],a[N],b[N],X[N],Y[N],g[N],rat[N];
    inline db slp(int j,int k){
        return fabs(X[j]-X[k])<eps?-inf:(Y[j]-Y[k])/(double)(X[j]-X[k]);
    }
    inline void rot(int x){
        int y=f[x],d=son(x),g=f[y];
        s[y][d]=s[x][!d]; f[s[y][d]]=y;
        if(g) s[g][son(y)]=x; f[x]=g;
        s[x][!d]=y; f[y]=x;
    }
    inline void splay(int x,int r=0){
        for(int p;(p=f[x])!=r;rot(x))
            if(f[p]!=r && son(x)==son(p)) rot(p);
        if(!r) rt=x;
    }
    inline void insert(int k){
        if(!rt){ rt=k; return; }
        for(int x=rt;;){
            int d=X[x]<X[k];
            if(!s[x][d]){ s[x][d]=k; f[k]=x; splay(k); return; }
            else x=s[x][d];
        }
    }
    inline int gPre(int k){
        int r=0;
        for(int x=s[k][0];x;){
            if(slp(x,k)<=l[x]+eps) r=x,x=s[x][1];
            else x=s[x][0];
        }
        return r;
    }
    inline int gSuc(int k){
        int l=0;
        for(int x=s[k][1];x;){
            if(slp(x,k)+eps>=r[x]) l=x,x=s[x][0];
            else x=s[x][1];
        }
        return l;
    }
    inline void ps(int k){
        int p;
        if(s[k][0]){
            p=gPre(k);
            splay(p,k);
            f[s[p][1]]=0;
            s[p][1]=0;
            l[k]=r[p]=slp(k,p);
        } else l[k]=inf;
        if(s[k][1]){
            p=gSuc(k);
            splay(p,k);
            f[s[p][0]]=0;
            s[p][0]=0;
            r[k]=l[p]=slp(k,p);
        } else r[k]=-inf;
    }
    inline void maintain(int k){
        splay(k); ps(k); int p;
        if(l[k]<=r[k]+eps){
            if(!s[k][0] || !s[k][1]){
                rt=s[k][0]+s[k][1];
                f[rt]=0; ps(rt);
            } else {
                p=gSuc(k);
                splay(p,k);
                rt=s[k][0];
                f[rt]=0;
                s[rt][1]=p;
                f[p]=rt;
                ps(p); ps(rt);
            }
        }
    }
    inline void out(int x){
        if(!x) return;
        out(s[x][0]);
        printf("%d ",x);
        out(s[x][1]);
    }
    inline int find(db k){
        if(!rt) return 0;
        int x=rt;
        for(;;){
            if(r[x]>k+eps) x=s[x][1]; else
            if(l[x]+eps<k) x=s[x][0];
            else return x;
        }
    }
    int main(){
        scanf("%d%lf",&n,g);
        for(int i=1;i<=n;++i) scanf("%lf%lf%lf",a+i,b+i,rat+i);
        for(int i=1;i<=n;++i){
            int j=find(-a[i]/b[i]);
            g[i]=max(g[i-1],a[i]*X[j]+b[i]*Y[j]);
        //  printf("%d
    ",j);
            Y[i]=g[i]/(a[i]*rat[i]+b[i]);
            X[i]=Y[i]*rat[i]; insert(i); maintain(i);// out(rt); puts("");
        }
        for(int i=n;i<=n;++i) printf("%.3lf
    ",g[i]);
    }



    C[柠檬]

    不得不说这题如果想到了关键一步就基本秒掉了,然而我并没有想到
    首先考虑一下怎么转移式子
    fi表示前i个贝壳能转化为多少柠檬,枚举j,发现各种无法转移
    因为我们不可能去枚举s0那怎么办呢
    注意到决策最优性,如果对于某一段,我们选取大小为s0的贝壳去做转化,那么这一段的开头和结尾必然也是s0否则我们将其分成独立的一段,一定更优
    这下马上可以转移了,写成方程就是

    fi=max{fj1+vi(sisj+1)2},vi=vj

    这里vi贝壳i的大小,si表示的是i是第几个大小为vi的贝壳
    那么对于每个v[i]我们维护一个单独的上凸壳,每次询问在上面二分即可,这里用vector节省空间

    #include<vector>
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define LL long long 
    #define db double
    using namespace std;
    vector<int> q[10010];
    int n,c[10010],v[100010]; LL f[100010],s[100010];
    inline LL y(int j){
        return f[j-1]+v[j]*s[j]*s[j]-2*s[j]*v[j];
    }
    inline db slp(int j,int k){
        return (y(j)-y(k))/(2.*v[j]*(s[j]-s[k]));
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",v+i);
            s[i]=s[c[v[i]]]+1; c[v[i]]=i;
        }
    //  for(int i=1;i<=n;++i){
    //      for(int j=1;j<=i;++j) if(v[i]==v[j])
    //          g[i]=max(g[i],g[j-1]+v[i]*(s[i]-s[j]+1)*(s[i]-s[j]+1));
    //  }
    //  for(int i=1;i<=n;++i) if(s[i]==1) q[v[i]].push_back(i);
        for(int i=1,j,k;i<=n;++i){
            k=v[i];
            if(q[k].size()<=1) q[k].push_back(i); else{
                for(int j=q[k].size()-1;j;--j)
                    if(slp(q[k][j],q[k][j-1])<slp(q[k][j],i)) q[k].pop_back(); else break;
                q[k].push_back(i);
            }
            if(q[k].size()==1) j=q[k][0];
            else {
                int l=0,r=q[k].size()-1;
                for(int m;l<r;){
                    m=l+r>>1;
                    if(slp(q[k][m],q[k][m+1])>=s[i]) l=m+1;
                    else r=m;
                }
                j=q[k][l];
            }
            f[i]=f[j-1]+v[i]*(s[i]-s[j]+1)*(s[i]-s[j]+1);
        }
        printf("%lld
    ",f[n]);
    }



    D[玩具装箱]

    题目难度从此开始下降
    对于题目里面已经给出的式子,我们直接把它翻译成代码即可

    fi=min{fj+(gigjL)2}
    这里gi=si+i,si表示前i个玩具长度之和
    这里写一下转化式子的步骤,设k<j<i
    fi=fj+(gigjL)2<fk+(gigkL)2
    fjfk<gk2gj2+2gigj2gigk2gjL+2gk+L
    fj+gj2+2gjLfkgk22gkL<2(gjgk)gi
    fj+gj2+2gjLfkgk22gkL2(gjgk)<gi

    后面题目不再重复上述过程

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define N 50010
    #define LL long long
    using namespace std;
    int n,L;
    LL s[N],g[N],f[N],q[N];
    inline db y(int j){
        return f[j]+g[j]*g[j]+2*g[j]*L;
    }
    inline db slp(int j,int k){ return (y(j)-y(k))/(2.*(g[j]-g[k])); }
    int main(){
        scanf("%d%d",&n,&L); ++L;
        for(int i=1;i<=n;++i){  
            scanf("%lld",s+i);
            s[i]+=s[i-1]; g[i]=s[i]+i;
        }
        int l=0,r=0; f[0]=0;
        for(int i=1,j;i<=n;++i){
            while(l<r && slp(q[l],q[l+1])<=g[i]) ++l; j=q[l];
            f[i]=f[j]+(g[i]-g[j]-L)*(g[i]-g[j]-L);
            while(l<r && slp(q[r-1],q[r])>slp(q[r],i)) --r; q[++r]=i;
        }
        printf("%lld
    ",f[n]);
    }



    E[仓库建设]

    经典题目之一
    这个题目我们考虑倒着做,因为直接计算距离不是非常方便
    先计算出只建立一个仓库的代价之和S
    fi表示最后一个仓库放在i的最大节约代价
    那么就可以得到一条非常简洁的转移式子

    fi=max{fj+(rjri)sici}

    这里ri是第i个工厂到第n个工厂的距离,si是1~i个工厂的产品数量之和

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define N 1000010
    #define LL long long
    using namespace std;
    int n,r[N],p[N],c[N],q[N];
    LL s[N],f[N],S=0,g[N];
    inline db slp(int j,int k){
        return (f[j]-f[k])/(db)(r[k]-r[j]);
    }
    int main(){
        scanf("%d",&n); 
        for(int i=1;i<=n;++i) scanf("%d%d%d",r+i,p+i,c+i),s[i]=s[i-1]+p[i];
        for(int i=1;i<n;++i) S+=(r[n]-r[i])*(LL)p[i]; S+=c[n];
        f[n]=0; int h=1,t=0; q[++t]=n;
        for(int i=n-1;i;--i){
            while(h<t && slp(q[h],q[h+1])>=s[i]) ++h;
            f[i]=f[q[h]]+(r[q[h]]-r[i])*s[i]-c[i];
            while(h<t && slp(q[t],q[t-1])<slp(q[t],i)) --t;
            q[++t]=i; *f=max(*f,f[i]);
        }
        printf("%lld
    ",S-*f);
    }   



    F[Cats Transport]

    终于来了几道CF的题
    和上面一道题一样,我们依然先计算出只用一个feeder时的等待时间之和
    让后再计算能优化多少,设fi,j表示用了i个feeder,最后一个在时刻j出发的答案
    那么就得到了和上题几乎一模一样的式子

    fi,j=max{fi1,k+(rkrj)sj}

    注意用滚动数组就可以了,基本没有什么变化

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define N 100010
    #define db double
    #define LL long long
    using namespace std;
    int n,m,k,q[N]; LL f[2][N],s[N],S,r[N],p[N],d[N],g[10][1000];;
    inline db slp(int j,int k){ return (f[0][j]-f[0][k])/(r[k]-r[j]); }
    int main(){
        scanf("%d%d%d",&n,&m,&k);
        for(int i=2;i<=n;++i) scanf("%lld",d+i),d[i]+=d[i-1];
        for(int x,i=1;i<=m;++i){
            scanf("%d%lld",&x,r+i); r[i]-=d[x];
        }
        sort(r+1,r+1+m); n=0; r[0]=-1000;
        for(int i=1;i<=m;++i){
            if(r[i]!=r[i-1]){ r[++n]=r[i]; p[n]=1; }
            else ++p[n];
        }
        for(int i=1;i<n;++i){ 
            S+=(r[n]-r[i])*(LL)p[i]; 
            s[i]=s[i-1]+p[i]; 
        }
        for(int T=1;T<k;++T){
            f[0][n]=0; int h=1,t=0; q[++t]=n;
            for(int i=n-1;i;--i){
                while(h<t && slp(q[h],q[h+1])>=s[i]) ++h;
                f[1][i]=f[0][q[h]]+(r[q[h]]-r[i])*s[i];
                while(h<t && slp(q[t],q[t-1])<slp(q[t],i)) --t;
                q[++t]=i;
            }
            swap(f[0],f[1]);
        }
        LL A=0;
        for(int i=1;i<=n;++i) A=max(A,f[0][i]);
        printf("%lld
    ",S-A);
    }



    G[Product Sum]

    也是一个比较简单的题
    我们设fi表示移动i能获得最大的贡献
    那么考虑把这个i移动到j这个位置,令si=j=1iai就得到转移式子

    fi=max{sisj+(ji)ai}

    注意到这里并没有i<j的限制,所以我们要选择一项:
    1.做两次
    2.先求凸包再二分
    肯定是第二项比较方便辣

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define LL long long
    using namespace std;
    int n,m,q[200010];
    LL f[200010],v[200010],s[200010],S;
    inline db slp(int j,int k){
        return (s[j]-s[k])/(j-k);
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%lld",v+i),s[i]=s[i-1]+v[i],S+=v[i]*i;;
        int t=0; q[++t]=0;
        for(int i=1;i<=n;++i){
            while(1<t && slp(q[t-1],q[t])>slp(q[t],i)) --t;
            q[++t]=i;
        }
        for(int i=1;i<=n;++i){
            int l=1,r=t;
            for(int m;l<r;){
                m=l+r>>1;
                if(slp(q[m],q[m+1])<v[i]) l=m+1;
                else r=m;
            }
            f[i]=s[i]-s[q[l]]+(q[l]-i)*v[i];
            *f=max(*f,f[i]);
        }
        printf("%lld
    ",*f+S);
    }



    H[Levels and Regions]

    这个题需要推一推式子,然而我期望这方面技巧极差
    首先推一推打通第i关所需要的时间Ti,设i所在的Region为[l,r]
    那么

    Ti=Tij=li1tjj=litj+1

    p=tij=litj那么就得到Ti=(1p)Ti+1就是Ti=1+p1
    那么整个Region的期望时间E(l,r)就是E(l,r)=i=lrj=litjti
    为了方便dp,我们将这个式子转化一下,首先设Si=j=1iti
    E(l,r)=i=lrSjSl1ti=i=lrSitiSl1i=lr1ti

    这里预处理一下Ai=j=1iSjtj,Bi=j=1i1tj就可以了
    最后的dp式子是这样的:
    fk,i=min{fk1,j+AiAjSj(BiBj)}
    剩下的就是套路而已

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define N 200010
    #define db double
    using namespace std;
    int n,k,q[N];
    db DP[2][N],AA[N],BB[N],s[N],v[N];
    inline db y(int j){
        return DP[0][j]-AA[j]+s[j]*BB[j];
    }
    inline db slp(int j,int k){
        return (y(j)-y(k))/(s[j]-s[k]);
    }
    int main(){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i){
            scanf("%lf",v+i);
            s[i]=s[i-1]+v[i];
            AA[i]=AA[i-1]+s[i]/v[i];
            BB[i]=BB[i-1]+1./v[i];
        }
        for(int i=1;i<=n;++i) DP[0][i]=AA[i];
        for(int T=2;T<=k;++T){
            int l=1,r=0; q[++r]=0;
            for(int i=1,j;i<=n;++i){
                while(l<r && slp(q[l],q[l+1])<BB[i]) ++l;
                j=q[l];
                DP[1][i]=DP[0][j]+AA[i]-AA[j]-s[j]*(BB[i]-BB[j]);
                while(l<r && slp(q[r-1],q[r])>=slp(q[r],i)) --r;
                q[++r]=i;
            }
            swap(DP[0],DP[1]);
        }
        printf("%.7lf
    ",DP[0][n]);
    }



    I[序列分割]

    回到Bzoj了
    发现这个题,无论什么顺序分割序列,最终答案只和分割位置有关
    那么计算一下发现就是每个块两两相乘,得到式子
    fi,j表示做到j,用了i块的最大结果
    fi,j=max{fi1,k+(sjsk)sk},si=j=1iai
    滚动数组就没了

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define LL long long
    using namespace std;
    int n,k,q[100010]; LL f[2][100010],s[100010];
    inline LL y(int j){ return f[0][j]-s[j]*s[j]; }
    inline db slp(int j,int k){
        if(s[k]==s[j]) return 1e100;
        return (y(j)-y(k))/(s[k]-s[j]);
    }
    int main(){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i) scanf("%lld",s+i),s[i]+=s[i-1];
        for(int i=1;i<=k;++i){
            int l=1,r=0; q[++r]=0;
            for(int j=1;j<=n;++j){
                while(l<r && slp(q[l],q[l+1])<s[j]) ++l;
                f[1][j]=f[0][q[l]]+(s[j]-s[q[l]])*s[q[l]];
                while(l<r && slp(q[r],q[r-1])>slp(q[r],j)) --r;
                q[++r]=j;
            }
            swap(f[0],f[1]);
        }
        printf("%lld
    ",f[0][n]);
    }



    J[特别行动队]

    前面那么多例题,这个题就不说了吧,注意a是负的就行了

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define db double
    #define LL long long
    using namespace std;
    int n,m,q[1000010],a,b,c;
    LL f[1000010],v[1200010],s[1200010],S;
    inline LL y(int j){ return f[j]+a*s[j]*s[j]-b*s[j]; }
    inline db slp(int j,int k){
        return (y(j)-y(k))/(s[j]-s[k]);
    }
    int main(){
        scanf("%d%d%d%d",&n,&a,&b,&c);
        for(int i=1;i<=n;++i) scanf("%lld",v+i),s[i]=s[i-1]+v[i];
        int l=1,r=0; q[++r]=0;
        for(int i=1;i<=n;++i){
            while(l<r && slp(q[l],q[l+1])>2.*a*s[i]) ++l;
            int j=q[l];
            f[i]=f[j]+a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c;
            while(l<r && slp(q[r-1],q[r])<slp(q[r],i)) --r; q[++r]=i;
        }
        printf("%lld
    ",f[n]);
    }
  • 相关阅读:
    数据库索引概念与优化
    数据库查询效率分析
    C语言结构体与C++结构体之间的差异
    判断一个序列是否为栈的弹出序列
    C语言中的结构体
    C++ STL 中的 std::sort()
    Spring注入值到静态变量
    层次遍历二叉树
    计算二叉树的大小
    计算二叉树的高度
  • 原文地址:https://www.cnblogs.com/Extended-Ash/p/9477067.html
Copyright © 2011-2022 走看看