DP 优化 - 决策单调性优化 dp
决策单调性与四边形不等式
决策单调性
解决形如 \(dp_i = \min_{j\in[1,i] w(j,i)}\) 的最优化问题时,令 \(\text{opt}_i\) 为 \(i\) 的最优决策点。
定义:
决策单调性指的是 \(\forall i_1, i_2, i_1 \lt i_2, \text{opt}_{i_1} \le \text{opt}_{i_2}\)。
证明:
考虑两个决策 \(j_1,j_2\) 在 \(i\) 增长的时候变化。
证明在 \(i \leftarrow i + 1\) 时决策点 \(j_2\) 的权值比决策点 \(j_1\) 的权值往最优的方向增长的更快,则决策点递增。
如在 \(\min\) 的最优化问题时:
\(\Delta w(j_1,i) \gt \Delta w(j_2,i) \Leftrightarrow\) 最优决策点单调递增
\(\Delta w(j_1,i) \lt \Delta w(j_2,i) \Leftrightarrow\) 最优决策点单调递减
\(\max\) 问题同理。
四边形不等式
四边形不等式指的是对于二元函数 \(w\),和 \(\forall a \le b \le c \le d\),满足:
俗称相交小于包含。
这个东西一般用作决策单调性的证明。
优化 dp 的实现
分治
void solve(int l, int r, int opt_l, int opt_r) 表示分治到区间 \([l,r]\),决策点是 \([\text{opt}_l, \text{opt}_r]\)。
每次分治里面找到 \([l,r]\) 的中点 \(mid\),求出 \(mid\) 的最优决策点 \(\text{opt}_{mid}\) 并记录。
最后递归分治处理:
solve(l, mid - 1, opt_l, opt_mid); 和 solve(mid + 1, r, opt_mid, opt_r);
该算法的局限性在于只能在两个不同的数组(决策点和被转移的)中进行,不能将一个数组转移到自己。
即必须知道决策点的 dp 值才可以转移。
二分队列
不要看 OI wiki!不要看 OI wiki!不要看 OI wiki!
推荐博客
适用性
贡献函数 \(w\) 斜率单调递增,求 \(\min\),或者反过来。
建立队列存储 \((j,l,r)\) 表示 \([l,r]\) 最优决策点是 \(j\)。判断队首是否过时(区间不包含 \(i\)),否则更新 \(l\leftarrow i\)。
如果队尾的决策不在 \(n\) 处优于 \(i\),那么可能需要弹出队尾。
队尾的 \(j\) 转移到 \(l\) 时劣于 \(i\),那么根据决策单调性,往后一直到 \(n\) 都劣,直接扔掉。
取出队尾,找到 \(p\) 满足 \(p\) 之前都从 \(j\) 转移更优,\(p\) 及往后都从 \(i\) 转移优。二分可以找到,最后更新队尾的 \(r \leftarrow p - 1\) 以及插入 \(i\)。

例题
\(dp_{i,j}\) 表示前 \(i\) 个村庄建了 \(j\) 的邮局的最小距离。
显然一个区间里选中位数位置作为邮局是最优的,具体证明方法可以考虑将邮局从中位数往两边移动而更劣。
\(dp_{i,j}=\min(dp_{i,j},dp_{k,j-1}+w(k+1,j))\)
\(w\) 具有决策单调性
二分队列即可实现,或者分治。
//https://www.cnblogs.com/alex-wei/p/DP_optimization_method_II.html —— 8.
const int N = 3e3 + 5, P = 305;
int n, p;
int pos[N], sum[N];
int dp[N][P];
struct node{
int j, l, r; // j 是区间 [l, r] 的最优决策
} q[N]; int L, R;
int w(int l, int r){
int mid = (l + r + 1) >> 1;
if((r - l + 1) % 2 == 1){
return sum[r] + sum[l - 1] - sum[mid - 1] - sum[mid];
}else{
return sum[r] + sum[l - 1] - sum[mid - 1] * 2;
}
}
int calc(node lst, int i, int k){ // 寻找 p 使得 p 以前的位置从 lst.j 转移更优,p 和它后面的位置从 i 转移更优
int l = lst.l, r = lst.r + 1;
while(l < r){
int mid = (l + r) >> 1;
if(dp[i][k] + w(i + 1, mid) <= dp[lst.j][k] + w(lst.j + 1, mid)){
r = mid;
}else{
l = mid + 1;
}
}
return r;
}
signed main(){
n = read(), p = read();
rep(i, 1, n){
pos[i] = read();
sum[i] = sum[i - 1] + pos[i];
}
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
rep(j, 1, p){
q[L = R = 1] = {0, 1, n};
rep(i, 1, n){
dp[i][j] = dp[q[L].j][j - 1] + w(q[L].j + 1, i); // 队首最优
q[L].l++; // 更新队首的 l
while(L <= R && q[L].l > q[L].r) L++; // 废物扔出去
if(dp[q[R].j][j - 1] + w(q[R].j + 1, n) > dp[i][j - 1] + w(i + 1, n)){ // 如果 q[R] 不一直优
while(L <= R && dp[i][j - 1] + w(i + 1, q[R].l) <= dp[q[R].j][j - 1] + w(q[R].j + 1, q[R].l)) R--; // 决策单调性,所有 q[R].l 就不如了的话后面都劣,直接扔了
if(L > R){
q[++R] = {i, i + 1, n};
continue;
}
int pos = calc(q[R], i, j - 1); // 找到 p
q[R].r = pos - 1; // 更新队尾的 r
q[++R] = {i, pos, n}; // 把 i 扔进去
}
}
}
write(dp[n][p]);
return 0;
}
这个鬼东西可以分治,但是打死都不想写了,给个题解
例题
CF833B
P4767

浙公网安备 33010602011771号