斜率优化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×aj−sj+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];
}