Loading

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\),满足:

\[w(a,d)+w(b,c) \ge w(a,c) + w(b,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\)

屏幕截图 2025-07-29 213708

例题

P4767 邮局

\(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

posted @ 2025-07-29 21:38  lajishift  阅读(37)  评论(0)    收藏  举报