例题 : HNOI玩具装箱
朴素的DP
由题意我们可以马上得到DP方程:
很可惜, 这个解法是(O(n^2))的, 无法AC (但是可以在当年骗很多分?)
接下来的文章中我们会用几个简称
第一类斜率优化
优化dp的思路有两种 :
- 优化状态转移方程
- 优化决策(排除不可能决策) 如:单调队列优化
我们无法再进行第一种优化了(方程是(1D/1D)的), 所以只能进行第二种优化
考虑i的两个决策点j,k, 其中 j < k
如果k优于j, 那么
这不就是斜率的代数式吗?
注意, 在此处用到了一个性质: 对于 k>j , X(k) - X(j) > 0
也就是DP(k) > DP(j)
这是第一类斜率优化的必要条件, 该题中可通过
打表得知.
记住, 我们现在得到的结果是:
当(j<k)时, 若满足$ frac {Y_k-Y_j} {X_k-X_j} leq 2f_i$ , 则 决策k优于决策j, 否则决策j 优于决策 k.
之后的文章里, "决策k优于决策j" 简写为 k B j.
通过这个结果, 我们可以得知: 可选决策的集合斜率单调递增, 形成一个下凸壳.
为什么? 下面是证明:
(设有3个决策x, y, z, 其中x<y<z)
$如果 hyp(x, y) > hyp(y, z) , 那么y不可能是最优决策点. $分两种情况讨论:
1: hyp(x, y) > 2*f(i)
该情况不满足上述不等式, 得到 x B y. 排除 y.
2: hyp(x,y) < 2*f(i)
该情况满足上述不等式, 得到 y B x.
但是由于 (hyp(x, y) > hyp(y, z)), 所以推出(hyp(y, z) < 2*f(i)), 得到 z B y
综上, y必定会被排除.
我们知道了可选决策集合, 那么怎么快速得知最优决策呢?
思考凸壳的性质.
可以得知在凸壳上必定有一点 y 它的前驱是x, 后继是z, 其中(hyp(x,y) < 2*f_i < hyp(y,z))
那么可以得知**y B x, y B z **
二分斜率 (2f_i)即可. (请读者自己思考全体斜率大于/小于(2f_i)的情况)
如何维护最优决策呢?
直接按照graham扫描法用单调栈(O(n))维护凸壳即可.
复杂度(O(n logn))
代码呼之欲出.
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){int ans = 0, f = 1; char c = getchar();while(c < '0' || c > '9') f = (c == '-') ? -1 : f, c = getchar();while('0' <= c && c <= '9') ans = ans*10 + c - '0', c = getchar();return ans;}
#define ld long double
#define int long long
const int maxn = 5e4+5;
int dp[maxn], sum[maxn];
int n, L;
inline int v1(int p){
return sum[p] + p;
}
inline int sqr(int x){return x*x;}
//hypotenuse
int v2;
inline ld hyp(int a, int b){
if(v1(a) == v1(b)) return -1e9;
return (ld)(dp[a] + sqr(v1(a)+v2) -dp[b] - sqr(v1(b)+v2)) / (v1(a) - v1(b));
}
int q[maxn*2], l, r;
void solve(){
l = 0, r = 1; q[++l] = 0;
v2 = 1+L;
for(int i = 1; i <= n; i++) {
//凸壳斜率单调递增
while(l < r && hyp(q[l], q[l+1]) < 2*v1(i) )
l++;
//这里我没有用二分, 因为斜率2*f(i)也是单调递增的.
//将单调栈改为维护单调队列就可以得到答案.
int opt = q[l];//optimal point
dp[i] = dp[opt] + sqr(v1(i) - v1(opt) - v2);
//printf("%lld
", dp[i]);
while(l < r && hyp(q[r-1], q[r]) > hyp(q[r], i)) r--;
q[++r] = i;
}
}
signed main(){
n = read(), L = read();
for(int i = 1; i <= n; i++){
int tmp = read();
sum[i] = sum[i-1] + tmp;
}
solve();
printf("%lld
", dp[n]);
return 0;
}
第二类斜率优化
题目: NOI2007 货币兑换
该题斜率方程不满足性质 : 对于 k>j , X(k) - X(j) > 0
看的出来这是一个二维偏序模型, 可用CDQ分治维护凸壳.
蒟蒻写挂了, 求神犇指教.
注意事项(Q/A)
Q: 什么时候可以用斜率优化?
A: 1D/1D方程, 决策单调不等式左边像个斜率, 右边 hyp 只与 (i) 有关
"像个斜率"意味着不等式左边不一定是$frac {Y_k-Y_j} {X_k-X_j} $, 也可以是 (frac {Y_k-Y_j} {X_j-X_k})
毕竟在证明的过程中, 我们没有用到任何斜率的性质, 一直是在推不等式 , 对吧?
Q: 怎么推式子?
A: 记住决策不等式前提是: k>j, 意义是:
当(j<k)时, 若满足$ frac {Y_k-Y_j} {X_k-X_j} (leq或geq ) hyp$ , 则 决策 k 优于决策 j , 否则决策 j 优于决策 k .
Q: 维护上/下凸壳?
A: 上式为大于号时 维护上凸壳, 为小于号时维护下凸壳.
关于打表
DP打表要打 2 个值:
- dp(i)
- dp(i)的决策点