斜率优化

斜率优化

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

方法是通过方程推出一个形似\(\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\times (X_i-X_j)+\sum_{k=j+1}^{i}{P_k\times(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\times(X_i-X_j))+C_i;\)

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

\(\therefore f_j+(s_i-s_j-sp_j\times(X_i-X_j))+C_i<F_k+(s_i-s_k-sp_k\times(X_i-X_k))+C_i\)

\(\therefore f_j-s_j-sp_j\times X_i+sp_j\times X_j<f_k-s_k-sp_k\times X_i+sp_k\times X_k\)

\(\therefore f_j-s_j+sp_j\times X_j-f_k+s_k-sp_k\times X_k<sp_j\times X_i-sp_k\times X_i\)

\(g_i=f_j-s_j+sp_j\times X_j\)

\(\therefore g_j-g_k<(sp_j-sp_k)\times X_i\)

不妨令\(sp_j<sp_k\)

\(\therefore\frac{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\)要更优\(\Leftarrow\Rightarrow\)\(A_jA_k\)的斜率小于\(X_i\)

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

1782021-20190831120516699-1977165046.png

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

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

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

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

1782021-20190831120418187-882175216.png

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

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

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

再看,\(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\n",F[n]);
}
ANOTHER CASE

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

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

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

ELSE IF…

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

转载于:https://www.cnblogs.com/OIER-Yu/p/11437022.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值