斜率优化DP


一,概述

针对一类DP递推式中出现多项式交叉的多项式的DP
一定要求具有求解单调性

1,OI Wiki 的描述:

  • 将初始状态入队
  • 每次使用一条和 i i i 相关的直线 f i f_i fi 去切维护的凸包,找到最优决策,更新 d p i dp_i dpi
  • 加入状态 d p i dp_i dpi。如果一个状态(即凸包上的一个点)在 d p i dp_i dpi加入后不再是凸包上的点,需要在 d p i dp_i dpi加入之前剔除

2,思考过程

1,把原始DP方程做出来
2,把常量提出,在其余需要转移的量中选取一个 和转移位置下标有关的数组值 ,作为自变量(一般希望它在X轴方向上具有单调性)
3, 剩余的未知量(一般是前面的Dp值),作为纵坐标,建立坐标系
4,在转移的过程中,一般通过选取的横坐标的系数(斜率)不变
5,建模线性规划,考虑定斜率直线切点集凸包
6,维护凸包,线性或LOG选取进行转移

  • 最终我们希望转移方程为以下形式: f [ i ] = m i n { f [ j ] + a [ i ] ∗ b [ j ] + c [ j ] + d [ i ] } f[i] = min\{f[j]+a[i]*b[j] +c[j]+d[i]\} f[i]=min{f[j]+a[i]b[j]+c[j]+d[i]}
  • 进一步的: f [ j ] + c [ j ] = a [ i ] ∗ b [ j ] + d [ i ] + f [ i ] f[j]+c[j] = a[i]*b[j]+d[i]+f[i] f[j]+c[j]=a[i]b[j]+d[i]+f[i]
  • 我们要最小化或者最大化的一般是斜率,考虑适当换元使得符合这样一个一次形式
  • k = a [ i ] k=a[i] k=a[i] 记作待决策值值
  • ( b [ j ]   ,   f [ j ] + c [ j ] ) (b[j]~,~f[j]+c[j]) (b[j] , f[j]+c[j]) 在函数散点图中出现,作为凸包的维护对象记作决策值
  • 整个DP 的过程就是在散点图中拟合待择取值并优化

3,具体构造

1,通过四边形不等式或者数学归纳证明求解单调性
2,根据单调性设出两个决策对象 j1,j2 并根据求解单调性对函数值列出不等式
3,提出不变量( a [ i ] a[i] a[i]) 并得到不等关系,以此择取值
4,结合线性规划做成下凸或者上凸

3.5,维护下凸壳的方法

  • 择取队尾和队尾之后一个元素,和要入队的元素比较,如果是这种构型,直接删去
    在这里插入图片描述

细节:
1,初始队列不为空,注意转移的起点需要赋初值
2,斜率是绑定在靠左的点上求的,我们需要第一个斜率大于目标的直线的左端点,注意弹栈的限制
3,队列至少两个元素!

4,一般建模:

1,数组有序,分组选取
2,在min,max择取的时候考虑通过排序保证择取的是端点值
3,等待问题考虑细分过程,明确时间底线使用前缀和择取

二,用法

以下的手法只是维护凸包的一个手段,我们不做lim,只是提出一个引子和启发

1,单调队列选取(k和b[i]单调)

n n n 个 任务 排成一个 序列,顺序不得改变,其中第 i i i 个 任务 的 耗时 为 t i t_i ti, 费用系数 为 c i c_i ci

现需要把该 n n n 个 任务 分成 若干批 进行加工处理

每批次的 段头,需要 额外消耗 S S S 的时间启动机器。每一个任务的 完成时间 是所在 批次 的 结束时间。

完成一个任务的 费用 为:从 0 0 0 时刻 到该任务 所在批次结束 的时间 t t t乘以 该任务 费用系数 c c c

彩铅的详细讲解

int main()
{
    cin >> n >> s;

    for (int i = 1; i <= n; i ++ )
    {
        cin >> t[i] >> c[i];
        c[i] += c[i-1];
        t[i] += t[i-1];
    }

    int hh = 0;
    int tt = 0;
    int q[N];
    q[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        while(hh < tt && f[q[hh+1]]-f[q[hh]] <= (s+t[i])*(c[q[hh+1]]-c[q[hh]]) )hh++;
        f[i] = f[q[hh]]-(s+t[i])*c[q[hh]]+s*c[n]+t[i]*c[i];
        while (hh < tt && (f[i]-f[q[tt]])*(c[q[tt]]-c[q[tt-1]]) <= (f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt]]))tt--;
        q[++tt]=i;
    }

    cout << f[n];

}

2,二分择取(k不单调,b[i]单调)

  • 如果目标斜率不单调了,我们就不能单调栈直接择取了
  • 但是凸包还是要维护,凸包的性质是一定的
  • 二分,斜率有序的凸包中查找第一个斜率大于目标的位置并转移
  • 二分的位置就是单调队列内部,注意左右边界的择取
int main()
{
    cin >> n >> s;

    for (int i = 1; i <= n; i ++ )
    {
        cin >> t[i] >> c[i];
        c[i] += c[i-1];
        t[i] += t[i-1];
    }

    int hh = 0;
    int tt = 0;
    q[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int l = hh;
        int r = tt;
        while (l<r)
        {
            int mid = l+r>>1;
            if(f[q[mid+1]]-f[q[mid]] > (s+t[i])*(c[q[mid+1]]-c[q[mid]]))r=mid;
            else l = mid+1;
        }

        f[i] = f[q[l]]-(s+t[i])*c[q[l]]+s*c[n]+t[i]*c[i];
        while (hh < tt && (s64)(f[i]-f[q[tt]])*(c[q[tt]]-c[q[tt-1]]) <= (s64)(f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt]]))tt--;
        q[++tt]=i;
    }

    cout << f[n];

}

3,CDQ 离线择取(k和b[i]均不单调)

4,Splay 择取(k和b[i]均不单调)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值