逸一时,误一世!|

MistZero

园龄:5年5个月粉丝:8关注:3

DP 优化

只是 DP 优化罢了,其他乱七八糟的 DP 根本不会。

全文只是我自己的理解,有逻辑上的错误请指出来 qwq

斜率优化 DP

记得相同斜率不要弹掉,相同斜率优先选最前面的进行转移(要求转移次数次数最少时)。

斜率优化的流程是这样的。

首先列出 DP 式子,接着钦定两个在当前位置之前的变量。
形式化地,当前转移目标为 i,钦定 1j1<j2<i
接着钦定 j1<j2j2 优于 j1

此处的 j1,j2 代表由 ji 的决策转移点。
因为 DP 题目一定具有最优子结构,所以最优的 dpi 必定由最优的 dpj 转移而来。
这里每一层决策顺次下来,显然具有答案单调性。
大家都做过滑动窗口吧,思想其实差不多。

所以,这时候就可以利用单调队列和一定的安排次序来使得答案最优化。

目的:在保证下标单调性的同时探究什么因素导致了答案的单调性。
这个因题目而异,但万变不离其宗:函数式思想和单调性

P3195 [HNOI2008] 玩具装箱:斜率优化入门。

这道题可以具体展现斜率优化的流程。


第 1 步,列出 DP 转移方程。

假设目前已经 DP 完了 [1,j],要从 j 直接转移到 i
那么新产生的段就是 [j+1,i]。根据定义,dpi=min{dpi,dpj+(xL)2}
此处 x=i(j+1)+k=j+1iCk=ij1+k=j+1iCk

这样暴力转移当然是 O(N3) 的。

但是看到区间静态和,直接优化掉一维循环,设 sumi=j=1iCj

那么有 x=ij1+sumisumj

那么朴素 O(N2) DP 转移方程即为

dpi=min{dpi,dpj+(ij1+sumisumjL)2}

看到 1N5×104,考虑优化。


第 1.5 步,让式子看起来更舒服。

真不是开玩笑。如果看到一堆不知所云的项,对我来说,斜率优化真没办法推。
当然,如果你的脑子非常好使,当然不需要这些乱七八糟的东西。

下面来设置一些函数,用来替换式子中的某一些项。

比如,这里使用 f(i) 来替换 sumi+i

那么原式化为

dpi=min{dpi,dpj+(f(i)f(j)1L)2}

嗯,看起来非常舒服。


第 2 步,拆式子。

这时候我们钦定的 j1j2 派上用场了。
在此设 1j1<j2<iN,且 j2 优于 j1

这道题里面的“优”就是代价小。

那么翻译成人话就是

dpj2+(f(i)f(j2)1L)2dpj1+(f(i)f(j1)1L)2

这时候,有技巧地拆开。

f(i)f(j)1L 拆成两半,一半含有 i,另一半是剩下所有项。
这里我拆成了 f(i)(f(j)+1+L)

那么可以开始拆了。

dpj2+f(i)22×f(i)×(f(j2)1L)+(f(j2)1L)2dpj1+f(i)22×f(i)×(f(j1)1L)+(f(j1)1L)2

接着把含有 i 的丢到左边,剩下的移项到右边。

2×f(i)×((f(j1)1L)(f(j2)1L))(dpj1+(f(j1)+L+1)2)(dpj2+(f(j2)+L+1)2)

合并一下

2×f(i)×(f(j1)f(j2))(dpj1+(f(j1)+L+1)2)(dpj2+(f(j2)+L+1)2)

这里观察到右边下标有相同的地方,令 g(i)=(f(i)+L+1)2

那么看到

2×f(i)×(f(j1)f(j2))(dpj1+g(j1))(dpj2+g(j2))

现在左边应变为只含 i 的项(因为要探究转移目标为 i 时答案的规律)

两边同除以 (f(j1)f(j2)) 得到

2×f(i)(dpj1+g(j1))(dpj2+g(j2))f(j1)f(j2)

(因为 f(i) 单调递增,f(j1)f(j2)<0,中间要变号)

观察到右侧 j1,j2 下标其实是对应的,想到求一次函数的斜率。

k=ΔyΔx

这不一个形式。。。

所以将 dpi+g(i) 视作 yi,右边就是两个决策点之间的斜率。

回到定义,j2 优于 j1
那么我们知道,一个决策点 j2 优于另一个决策点 j1 的条件就是

2×f(i)(dpj1+g(j1))(dpj2+g(j2))f(j1)f(j2)

其实因为 j2>j1,写成这样更严谨:

2×f(i)(dpj2+g(j2))(dpj1+g(j1))f(j2)f(j1)

这玩意儿可以拿单调队列优化。

由于 f(i) 单调递增,每一次找到的第一个符合条件的 j,必定是最优决策。
随着 f(i) 单调递增,斜率显然也单调递增。

最后,如果有不符合单调递增规律的尾元素,直接删掉,最后插入 i

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 5e4 + 10;
int n, L, h, t, q[N], sum[N], dp[N];

inline int f(int x) { return sum[x] + x; }
inline int g(int x) { return (f(x)+L+1) * (f(x)+L+1); }
inline double slope(int j1, int j2) {
  return 1. * ((g(j2)+dp[j2]) - (g(j1)+dp[j1])) / (f(j2)-f(j1));
}

signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> n >> L; h = t = 1;
  for (int i = 1; i <= n; ++i)
    cin >> sum[i], sum[i] += sum[i-1];
  for (int i = 1; i <= n; ++i) {
    while (h < t && 2 * f(i) >= slope(q[h], q[h+1])) ++h;
    dp[i] = dp[q[h]] + (f(i)-f(q[h])-L-1) * (f(i)-f(q[h])-L-1);
    while (h < t && slope(q[t], i) <= slope(q[t-1], q[t])) --t;
    q[++t] = i;
  }
  cout << dp[n] << endl;
  return 0;
}

本文作者:MistZero

本文链接:https://www.cnblogs.com/MistZero/p/Accelerated-DP.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   MistZero  阅读(49)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起