zoukankan      html  css  js  c++  java
  • 斜率优化DP(学习笔记)

    膜拜yyb大佬的总结

    因为上面yyb大佬把斜率优化的板子讲得很清楚,所以我就不再赘述了.就来看几套模板题吧.

    蓝书和一本通提高篇上都是以"任务安排"这道题为例题层层递进讲解斜率优化的.

    洛咕上的弱化版,不用斜率优化也能过

    POJ上的普通版:斜率优化模板

    BZOJ上的加强版:斜率优化+二分

    题意:N个任务,每个任务有一个完成所需时间(t_i),试将这N个任务分成若干批,在每批任务开始前,机器需要启动时间S,这批任务完成所需时间为各个任务需要时间的总和(同一批任务将在同一时刻完成).每个任务的费用是它的完成时刻乘上一个费用系数(c_i).求最小总费用.

    (f[i])表示把前i批任务分成若干批的最小费用.运用"费用提前计算"的思想,有如下转移方程:

    (f[i]=min_{0<=j<i}f[j]+sumt[i]*(sumc[i]-sumc[j])+S*(sumc[N]-sumc[j]))

    只能过洛咕上的(N^2)做法:

    const int N=5005;
    int sumt[N],sumc[N],f[N];
    int main(){
        int n=read(),S=read();
        for(int i=1;i<=n;i++){
    		int t=read(),c=read();
    		sumt[i]=sumt[i-1]+t;
    		sumc[i]=sumc[i-1]+c;
        }
        memset(f,0x3f,sizeof(f));f[0]=0;
        for(int i=1;i<=n;i++)
    		for(int j=0;j<i;j++)
    	    	f[i]=min(f[i],f[j]+sumt[i]*(sumc[i]-sumc[j])+S*(sumc[n]-sumc[j]));
        printf("%d
    ",f[n]);
        return 0;
    }
    
    

    POJ上(N^2)做法不能过了,考虑对转移式优化.(f[i]=min_{0<=j<i}f[j]+sumt[i]*(sumc[i]-sumc[j])+S*(sumc[N]-sumc[j]))

    (k<j),且j比k更优,即,

    (f[j]+sumt[i]*(sumc[i]-sumc[j])+S*(sumc[N]-sumc[j])<f[k]+sumt[i]*(sumc[i]-sumc[k])+S*(sumc[N]-sumc[k]))

    乱搞一下这个式子得到,

    (frac {f[j]-f[k]}{sumc[j]-sumc[k]}<S+sumt[i])

    能过POJ的做法(O(N)):

    const int N=300005;
    int sumt[N],sumc[N],q[N],f[N];
    int main(){
        int n=read(),S=read();
        for(int i=1;i<=n;i++){
    		int t=read(),c=read();
    		sumt[i]=sumt[i-1]+t;
    		sumc[i]=sumc[i-1]+c;
        }
        memset(f,0x3f,sizeof(f));f[0]=0;
        int l=1,r=1;q[1]=0;
        for(int i=1;i<=n;i++){
    		while(l<r&&(f[q[l+1]]-f[q[l]])<=(S+sumt[i])*(sumc[q[l+1]]-sumc[q[l]]))l++;
    		f[i]=f[q[l]]+(sumt[i]*sumc[i]+S*sumc[n])-(S+sumt[i])*sumc[q[l]];
    		while(l<r&&(f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]])>=(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))r--;
    		q[++r]=i;
        }
        printf("%d
    ",f[n]);
        return 0;
    }
    
    

    题目加强:任务的执行时间(t_i)可能是个负数,这说明(sumt[i])不再保证单调性,所以我们必须要维护整个队列,队头也不一定最优,每次转移时需要二分查找,找到一个位置j,j左侧线段的斜率比(S+sumt[i])小,j右侧线段的斜率比(S+sumt[i])

    能过BZOJ的做法:

    const int N=300005;
    LL sumt[N],sumc[N],q[N],f[N];
    inline int erfen(int i,int j,int l,int r){
        if(l==r)return q[l];
        while(l<r){
    		int mid=(l+r)>>1;
    		if(f[q[mid+1]]-f[q[mid]]<=j*(sumc[q[mid+1]]-sumc[q[mid]]))l=mid+1;
    		else r=mid;
        }
        return q[l];
    }
    int main(){
        int n=read(),S=read();
        for(int i=1;i<=n;i++){
    		int t=read(),c=read();
    		sumt[i]=sumt[i-1]+t;
    		sumc[i]=sumc[i-1]+c;
        }
        int l=1,r=1;
        for(int i=1;i<=n;i++){
    		int j=erfen(i,S+sumt[i],l,r);
    		f[i]=f[j]+(sumt[i]*sumc[i]+S*sumc[n])-(S+sumt[i])*sumc[j];
    		while(l<r&&(f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]])>=(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))r--;
    		q[++r]=i;
        }
        printf("%lld
    ",f[n]);
        return 0;
    }
    
    

    个人对斜率优化的一些理解:首先前置知识---单调队列一定要掌握.其次因为毕竟是DP的优化方法之一,拿到题目,我们要先设好状态,列出转移方程(斜率优化的题目,(O(n^2))的转移方程一般都很容易得到).

    得到转移方程之后考虑整理这个式子,一般都要用到前缀和,然后一个普遍的套路就是yyb大佬也提到了的"设k<j且j比k更优",然后就能得到一个关于j和k与一个常数的不等式,这时就可以进行斜率优化了.

    然后一些细节问题需要自己领悟,比如维护的单调队列是递增还是递减?前缀和是否要开long long?维护的时候肯定要比较大小,大多数人一般都是写个函数用double,可是我个人在写下面"Print Article"这道题时用double过不去,换成long long就过了,像这种比较玄学的东西,就拼人品了,大不了挨个试一遍.

    [HNOI2008]玩具装箱TOY

    [APIO2010]特别行动队

    Print Article

    [CEOI2004]锯木厂选址

    [ZJOI2007]仓库建设

  • 相关阅读:
    Android AHandle AMessage
    android java 与C 通过 JNI双向通信
    android 系统给应用的jar
    UE4 unreliable 同步问题
    UE4 difference between servertravel and openlevel(多人游戏的关卡切换)
    UE4 Run On owing Client解析(RPC测试)
    UE4 TSubclassOf VS Native Pointer
    UE4 内容示例网络同步Learn
    UE4 多人FPS VR游戏制作笔记
    UE4 分层材质 Layerd Materials
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11007448.html
Copyright © 2011-2022 走看看