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]仓库建设

  • 相关阅读:
    使用virtualenvwrapper隔离python环境
    Ubuntu创建launcher
    Python单步调试
    Jupyter增加内核
    扩展User增加部门字段
    EasyUI ComboBox默认值
    C#调用dll时的类型转换
    更改VisualStudio默认创建类和接口不加public问题
    IL学习资料
    服务注册中心,Eureka比Zookeeper好在哪里?
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11007448.html
Copyright © 2011-2022 走看看