• 斜率优化


    斜率优化

    斜率优化是指对一些特殊的动态规划问题进行的优化(废话),数形结合,通过状态建点,利用其斜率的特性,在短时间求出最佳决策的算法。

    方法是通过方程推出一个形似(frac{g_j-g_k}{g'_j-g'_k}<K_i)的不等式

    话不多说,从例题入手

    BZOJ1096 [ZJOI2007]仓库建设 是一道入门题

    设子状态(f_i)为在工厂(i)建立仓库时(1)(i)的花费总和

    显然,(f_i=min{f_j+sum_{k=1}^{j}P_k imes (X_i-X_j)+sum_{k=j+1}^{i}{P_k imes(X_i-X_k)}+C_i})

    可以据此写出代码:

    for(int i=1;i<=n;i++)
    		sp[i]=sp[i-1]+P[i],s[i]=s[i-1]+sp[i-1]*(X[i]-X[i-1]);
    for(int i=1;i<=n;i++){
    	F[i]=INF;
    	for(int j=0;j<i;j++){
    		F[i]=min(F[i],F[j]+(s[i]-s[j]-sp[j]*(X[i]-X[j]))+C[i]);
    	}
    }
    

    复杂度为(O(n^2)),对于(N≤1000000)的数据显然是过不了的

    可以发现,为了得到子状态(f_i),共进行了(i)次转移,但是实际有效的只有一次

    如何可以在(O(1))(O(log_2n))的复杂度内找出最佳决策呢?

    斜率优化!

    对于转移方程(f_i=f_j+(s_i-s_j-sp_j imes(X_i-X_j))+C_i;)

    我们取出两个子状态(f_i)(f_k),假设从(f_j)转移到(f_i)比从(f_j)转移到(f_k)要更优

    ( herefore f_j+(s_i-s_j-sp_j imes(X_i-X_j))+C_i<F_k+(s_i-s_k-sp_k imes(X_i-X_k))+C_i)

    ( herefore f_j-s_j-sp_j imes X_i+sp_j imes X_j<f_k-s_k-sp_k imes X_i+sp_k imes X_k)

    ( herefore f_j-s_j+sp_j imes X_j-f_k+s_k-sp_k imes X_k<sp_j imes X_i-sp_k imes X_i)

    (g_i=f_j-s_j+sp_j imes X_j)

    ( herefore g_j-g_k<(sp_j-sp_k) imes X_i)

    不妨令(sp_j<sp_k)

    ( hereforefrac{g_i-g_k}{sp_j-sp_k}<X_i)

    仔细观察,发现这很像一个斜率的表达式

    在平面中建点(A_j(sp_j,g_j))(A_k(sp_k,g_k)),连接(A_jA_k)

    问题就可以转换为:

    对于子状态(f_i)(f_k)

    (f_j)转移到(f_i)比从(f_j)转移到(f_k)要更优(LeftarrowRightarrow)(A_jA_k)的斜率小于(X_i)

    那么,当平面中有多个点时又如何呢?

    如图,有点(A)(B)(C)(BC)斜率小于(AB)斜率

    如果(AB)斜率大于等于(X_i),则(A)(B)

    如果(AB)斜率小于(X_i),则(BC)斜率一定小于(X_i),则(C)(B)

    综上,出现如图情况时,(B)一定不是最优

    所以显然,在平面中有多个点时,只有下凸壳(如果方程求的是最大值则为上凸壳)的点才是有用的点

    此时,我们发现,点之间的斜率是单调递增的,

    这。。。不就是个单调队列/栈吗!

    再看,(X_i)是递增的,所以最佳状态在单调队列/栈里的位置也是递增的。

    所以就是单调队列了。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #define int long long
    #define maxn 1000000
    #define INF 0x3f3f3f3f3f3f3f3f
    using namespace std;
    void read(int &x){
    	int 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;
    }
    int X[maxn+5],P[maxn+5],C[maxn+5];
    int F[maxn+5],s[maxn+5],sp[maxn+5];
    struct vec{						//。。之前写凸包时用的结构体名是vec,现在就继续沿用了
    	int i,x,y;
    	vec(){i=x=y=0;}
    	vec(int x,int y){i=0,this->x=x,this->y=y;}
    	friend vec operator-(vec a,vec b){return vec(a.x-b.x,a.y-b.y);}
    	friend bool operator<(vec a,vec b){return (double)a.y/a.x<(double)b.y/b.x);}
    } q[maxn+5];
    int head=1,tail=0;
    void push(vec x){
    	while((tail-head+1)>1&&(x-q[tail-1])<(q[tail]-q[tail-1])) tail--;	//x和队尾上一个的斜率小于队尾和队尾上一个的斜率时,弹出队尾
    	q[++tail]=x;
    }
    void pop(int k){
    	while((tail-head+1)>1&&(q[head+1]-q[head])<vec(1,k)) head++;//弹出斜率比k小的斜率
    }
    #undef int
    int main(){
    #define int long long
    	int n;read(n);
    	for(int i=1;i<=n;i++) read(X[i]),read(P[i]),read(C[i]);
    	for(int i=1;i<=n;i++)
    		sp[i]=sp[i-1]+P[i],s[i]=s[i-1]+sp[i-1]*(X[i]-X[i-1]);
    	push(vec(0,0));
    	for(int i=1;i<=n;i++){
    		pop(X[i]);
    		int j=q[head].i;
    		F[i]=F[j]+(s[i]-s[j]-sp[j]*(X[i]-X[j]))+C[i];
    		vec x(sp[i],F[i]-s[i]+sp[i]*X[i]);x.i=i;
    		push(x);
    	}
    	printf("%lld
    ",F[n]);
    }
    

    ANOTHER CASE

    那么,如果(X_i)不是递增的呢?

    此时,显然不能靠移动单调队列队头来得到最佳状态了,只好将其改成了一个二分搜索(两点间斜率为值)搜索满足(A_{j-1}A_j)的斜率(<K_i(X_i))的最大的(j)了。

    例题:(突然找不到了,到时候再补)

    ELSE IF…

    如果(sp_i(g'_i))不是递增的呢?…请看下篇——CDQ分治套斜率优化

  • 相关阅读:
    RHCE考试要求
    c语言:md5函数
    c语言:计算输入字符个数
    IP数据报之Internet Header Length
    常用的tar和rpm命令参数
    Oracle数据库实例的创建、删除、修改【转载】
    Internal类或Internal成员讲解
    序列化与反序列化 BinaryFormatter.Serialize 方法 (Stream, Object)
    oracle网络配置listener.ora、sqlnet.ora、tnsnames.ora
    Oracle启动模式及其常见问题探讨
  • 原文地址:https://www.cnblogs.com/OIER-Yu/p/11437022.html
Copyright © 2020-2023  润新知