• 斜率优化


    这篇文章使用markdown 和 latex 写成。博客园对markdown的支持不是太完善,如果显示异常,请刷新页面


    斜率优化动态规划

    以前写过一篇关于动态规划斜率优化的文章,但是非常不好懂T_T,这两天做了一些斜率优化的题,再总结一下:

    例题:HDU 3507

    首先这个题朴素的DP方程是这样的:
    (f_i=min(f_j+sum_{k=j+1}^i(cost_k)+M)
    如果我们记(cost)的前缀和为(s),那么
    $f_i=min(f_j+(s_i-s_j)^2)+M ( 化简得到: )f_i=min(f_j+s_i2+s_j2-2s_is_j)+M( 注意到)s_i(只与)f_i(有关,所以可以从括号内提出 )f_i=min(f_j+s_j2-2s_is_j)+M+s_i2( 所以决策的表达式就是 )f_j+s_j^2-2s_is_j$


    现在我们考虑任意两个决策点(a)(b)(也就是说(j=a)(j=b)的情况)
    假设(a < b)
    那么决策(a)比决策(b)更优的条件就是
    (f_a+s_a^2-2s_is_a < f_b+s_b^2-2s_is_b)
    整理得到
    ((f_a+s_a^2)-(f_b+s_b^2) < 2s_i(s_a-s_b))
    继续整理,得到

    [frac{(f_a+s_a^2)-(f_b+s_b^2)}{s_a-s_b} < 2s_i ]

    这就是决策(a)比决策(b)更优的条件
    仔细观察这个式子,如果我们把(f_a+s_a^2)(f_b+s_b^2)分别看做点A和B的纵坐标,(s_a)(s_b)看做点A和B的横坐标,那么不等式左面就可以看成是一个斜率式。斜率优化的名字就由此而来。
    下文中我们就把不等式左面记做(k_{a,b})


    我们再来考虑三个决策(a,b,c(a < b < c)).
    如果有 (k_{a,b} < k_{b,c}) 那么意味着什么呢?
    判断两个决策谁更优需要和(s_i)比较,我们分三种情况讨论:

    • (k_{a,b} < k_{b,c} < s_i)
      因为(k_{a,b} < s_i),所以决策(a)决策(b)更好
      因为(k_{b,c} < s_i),所以决策(b)决策(c)更好
      综上我们在(a,b,c)中我们应该选择决策(a)
    • (s_i < k_{a,b} < k_{b,c})
      这种情况下决策(b)比决策(a)好,决策(c)比决策(b)好,所以我们应该选择决策(c)
    • $ k_{a,b} < s_i < k_{b,c}( 这种情况下决策)a(比决策)b(好,决策)c(也比决策)b(好,虽然我们不能确定决策)a(和决策)c(谁更好,但是肯定能确定决策)b(是不好的,不用考虑决策)b$了

    通过以上三种情况,我们发现只要有 (k_{a,b} < k_{b,c}),决策(b)就一定不是最好的,不用考虑了
    基于这一点,我们有效地减少了需要考虑的决策数,从而对这类DP进行了优化。


    那么具体怎么实现呢?

    如果我们把各个决策以点((s_j,f_j+s_j^2))的形式画在平面上,并且对于任意三个点A,B,C(按照横坐标A < B < C)都保证(k_{a,b} < k_{b,c}) 不成立(换句话说我们删去所有使得(k_{a,b} < k_{b,c})的B点),那么我们就会发现剩下的图形是一个凸多边形。

    也就是说,如果把各个决策看成是点,我们实际上要维护的是这些点的凸包。

    具体实现的时候,分两种情况:

    1.像这道题一样,决策点是依次出现的(横坐标依次增大),那么就非常简单了:

    我们维护一个单调队列,每次算完一个(f)的值,就把其对应的决策点加入单调队列队尾,把这个决策点看做是C,如果单调队列中有两个点或以上,就把最后的两个点看做是A和B,如果(k_{a,b} < k_{b,c})那么就把单调队列中最后一个点删去,直到(k_{a,b} < k_{b,c}) 不成立为止,加入这个新的决策点
    每次需要计算(f_i)值的时候,首先维护一下队头,如果队列中有两个或以上元素,且第一个点和第二个点的斜率值 $ < s_i(的话,就删去队头。*因为)s_i(是递增的,现在) < s_i(以后肯定) < s_{i+1}$,所以这样维护是合理的。*

    这样维护过后,直接取队头的决策就是当前最优的决策。

    如果你想不通为什么队头就是最优决策的话,画一张图看看。因为维护过的图形是一个上凸包,所以所有的斜率都是随着横坐标的增大而递减的,又因为第一个斜率$ < s_i(,所以所有的斜率都) < s_i(。那么从队头开始每个点都优于他后面一个点(因为这个斜率) < s_i$),根据传递性队头的点就是最优的了。

    这种情况下状态数仍然是(O(n))的,但转移的时间复杂度从(O(n))下降到(O(1))。又因为单调队列均摊下俩是(O(1))的,所以整体时间复杂度就从(O(n^2))优化到了(O(n))

    2.如果各个决策点出现的顺序是无须的,比如bzoj1492,那么就不能简单的用一个单调队列维护了,我们需要一颗平衡树,每次找到决策需要插入的位置,并分别向左向右维护凸包。这变成了一个经典的动态凸包问题。当然,这个题有其他不用斜率优化的更好的做法。


    以上就是斜率优化的全部原理和实现方法。附上hdu3507的代码以供参考:

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <iostream>
    
    #define MAXN (500000+10)
    
    using namespace std;
    int s[MAXN],a[MAXN],f[MAXN];
    struct node{
    	int x,y,ss;
    	node(int x=0,int y=0,int ss=0):x(x),y(y),ss(ss) {}
    };
    struct Mono_queue{
    	node t[MAXN];
    	int f,r;
    	int size;
    	void init(){
    		memset(t,0,sizeof t);
    		f=0;
    		r=0;
    		size=0;
    	}
    	void push(node x){
    		while (size>=2){
    			if ((x.x-t[r-1].x >0 && t[r-1].x-t[r-2].x>0)  || (x.x-t[r-1].x <0 && t[r-1].x-t[r-2].x<0)){
    				if ((x.y-t[r-1].y)*(t[r-1].x-t[r-2].x)<=(t[r-1].y-t[r-2].y)*(x.x-t[r-1].x))	r-- , size--;
    				else break;
    			}else {
    				if ((x.y-t[r-1].y)*(t[r-1].x-t[r-2].x)>=(t[r-1].y-t[r-2].y)*(x.x-t[r-1].x))	r-- , size--;
    				else break;
    			}
    		}
    		t[r++]=x;
    		size++;
    	}
    	void maintain(int x){
    
    		while (size>=2){
    			if ((t[f+1].x-t[f].x)>0){
    				if ((t[f+1].y-t[f].y)<=x*(t[f+1].x-t[f].x))	f++ , size--;
    				else break;
    			}else{
    				if ((t[f+1].y-t[f].y)>=x*(t[f+1].x-t[f].x))	f++ , size--;
    				else break;
    			}
    		}
    	}
    	int top(){
    		return t[f].ss;
    	}
    }T;
    int main (int argc, char *argv[])
    {
    	int m,n;
    	while (scanf("%d%d",&n,&m)!=EOF){
    		T.init();
    		memset(f,0,sizeof f);
    		for (int i=1;i<=n;i++)	scanf("%d",&a[i]);
    		for (int i=1;i<=n;i++)	s[i]=s[i-1]+a[i];
    		T.push(node(0,0,0));
    		for (int i=1;i<=n;i++){
    			T.maintain(2*s[i]);
    			int ss=T.top();	
    			f[i]=f[ss]+s[ss]*s[ss]-2*s[ss]*s[i]+s[i]*s[i]+m;
    			T.push(node(s[i],f[i]+s[i]*s[i],i));
    		}
    		printf("%d
    ",f[n]);
    	}
    	return 0;
    }
    

    斜率优化的题目都是大同小异,DP 的形式都差不多,只要能整理成斜率式,就能斜率优化。如果变量分离不开,那就不能斜率优化了。

  • 相关阅读:
    在c#中使用全局快捷键
    把其他C/C++编译器集成到VC2005中
    零基础学习Oracle 10G视频教程
    异常处理 Exception
    序列化与反序列化 BinaryFormatter二进制(.dat)、SoapFormatter(.soap)、XmlSerializer(.xml)
    MVC 数据验证
    MVC 路由规则
    分部类,分部方法 修饰符partial
    HttpRuntime类
    MVC 模型绑定
  • 原文地址:https://www.cnblogs.com/loveidea/p/3921210.html
Copyright © 2020-2023  润新知