0
点赞
收藏
分享

微信扫一扫

斜率优化DP

西红柿上校 2022-04-21 阅读 37
算法

斜率优化DP


概述

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

OI 的描述:

  • 将初始状态入队
  • 每次使用一条和 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加入之前剔除

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

  • 最终我们希望转移方程为以下形式,变量是一个一次函数:
    f i , j = j × a j − s j + m i n { y   −   k x } f_{i,j}=j×a_j−s_j+min\{y~−~kx\} fi,j=j×ajsj+min{y  kx}

  • 我们要最小化或者最大化的一般是斜率,考虑适当换元使得符合这样一个一次形式

一般建模:

1,数组有序,分组选取
2,

用法

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

1,单调队列选取(线性择取)

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

彩铅的详细讲解

维护下凸壳的方法:

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

细节:
1,初始队列不为空,置入一个0作为初值
2,斜率是绑定在靠左的点上求的,我们需要第一个斜率大于目标的直线的左端点,注意弹栈的限制
3,队列可以为空!

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,二分择取(LOG 择取)

  • 如果目标斜率不单调了,我们就不能单调栈直接择取了
  • 但是凸包还是要维护,凸包的性质是一定的
  • 二分,斜率有序的凸包中查找第一个斜率大于目标的位置并转移
  • 二分的位置就是单调队列内部,注意左右边界的择取
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 离线择取

4,Splay 择取

举报

相关推荐

0 条评论