• 关于决策单调性优化动态规划


    今天考场上突现决策单调性

    原本对这个算法表示摒弃的本弱突然被打击了

    于是来学习学习...

    原理

    我们只考虑$1D;|;1D$的动态规划...

    同时,我们讨论这么一类$dp$:$f[i] = min(f[j] + w(j, i))(1 leqslant j leqslant i - 1)$

    ($max$同理)

    我们记$w(i, j)$表示从$i$转移到$j$的代价

    决策单调性是指对于$a < b < c < d$

    如果$c$从$b$转移过来比从$a$转移过来更优

    那么$d$从$b$转移过来比从$a$转移过来更优

    用两个不等式来表达:

    $$f[b] + w(b, c) leq f[a] + w(a, c)...(1)$$

    $$f[b] + w(b, d) leq f[a] + w(a, d)...(2)$$

    如果我们有$$w(b, d) - w(b, c) leq w(a, d) - w(a, c)...(3)$$

    那么我们就能由$(1) + (3)$得到$(2)$

    考虑对$(3)$式化简,即$$w(b, d) + w(a, c) leq w(a, d) + w(b, c)...(4)$$

    这就是著名的四边形不等式

    如果我们考虑用图形来表达,那么可以简记为“交叉”和“包含”的关系

    这张图十分的形象

    一般而言,$1D ;|1D$决策单调性的特点是没有特点

    大致意思是,如果存在一个$dp$方程满足$1D;|;1D$

    但是无法用斜率优化 / 前缀和 / $wqs$二分 / 数据结构优化...那么就可以考虑决策单调性

    比较著名的例题

    $[HNOI2008]$玩具装箱

    非常显然的有$f[i] = min(f[j] + (i - j - 1 - L + s[i] - s[j])^2) (0 leqslant j < i)$

    令$w(j, i) = (i - j - 1 - L + s[i] - s[j])^2$

    我们考虑证明$w(a, c) + w(b, d) leq w(a, d) + w(b, c)$

    $w(a, c) + w(b, d) = (c - a - L + s[c] - s[a])^2 + (d - b - L + s[d] - s[b])^2$

    $w(a, d) + w(b, c) = (d - a - L + s[d] - s[a])^2 + (c - b - L + s[d] - s[c])^2$

    对比上下两式,我们就能证明了

    式子太长了不写了

    我们引入一道平时训练的题,即$CF868F...$

    非常明显的,我们设$f[i][j]$表示$1 sim i$中,划分了$j$段的最小代价

    那么有$f[i][k] = min(f[j][k - 1] + w(j, i))(1 leqslant j leqslant i)$

    我们考虑证明$w(b, d) + w(a, c) leq w(b, c) + w(a, d)$,就能证明决策单调性

    我们设第$i$种颜色在段$[a, b)$中出现了$x$次,在$[b, c)$中出现了$y$次,在$[c, d)$中出现了$z$次

    那么$w(b, d) + w(a, c) = inom{x + y}{2} + inom{y + z}{2}$

    同时$w(a, d) + w(b, c) = inom{x + y + z}{2} + inom{y}{2}$

    左式$ = x^2 + 2y^2 + z^2 + 2xy + 2yz - x - 2y - z$

    右式$ = x^2 + 2y^2 + z^2 + 2xy + 2yz + 2xz - x - 2y - z$

    那么如果左式$leq$右式,那么有$-x-2y-z leq 2xz - x - 2y - z$

    这显然成立,由于对每个颜色都满足这个不等式,因此四边形不等式是成立的

    所以证明决策单调性没有想象中的那么困难...

    实现决策单调性

    我们分两种情况来讨论,我们考虑有$n$个决策点和$m$个被决策点

    第一种情况,决策点和被决策点互相独立(yjq教会了我分治,却没有告诉我它的作用是有限的)

    即,被决策点在将来不会成为决策点

    这时,我们可以考虑用分治来解决,复杂度为$O(n log m + m)$

    void solve(int l, int r, int L, int R) {   
        if(l > r) return;
        //现在我们知道[L, R]的点可以决策[l, r]的点
        int pos = -1, mid = (l + r) >> 1;
        //我们寻找出mid的最优决策点
        for(int i = L; i <= min(R, mid - 1); i ++)
            if(g[i] + w(i, mid) < f[mid]) 
                f[mid] = g[i] + w(i, mid), pos = i;
        //g与f无关!!!
        //w(i, j)表示从i转移到j的代价
        //找出mid的最优决策点后
        //[l, mid - 1]的决策点区间落在[L, pos]中
        //[mid + 1, r]的决策点区间落在[pos, R]中
        solve(l, mid - 1, L, pos);
        solve(mid + 1, r, pos, R);
        //递归即可
    }

    同时在这时,如果$w(i, j)$不好$O(1)$的计算

    但是,用类似于莫队的方式十分好维护

    那么我们仍然可以在$O(n log m + m log m)$的时间内解决这个问题

    int nl, nr;
    int w(int i, int j) {
        while(nl > i) ...;
        while(nl < i) ...;
        while(nr > j) ...;
        while(nr < j) ...;
        return ...;
    }
    
    void solve(int l, int r, int L, int R) {
        if(l > r) return;
        int pos = -1, mid = (l + r) >> 1;
        for(int i = L; i <= min(R, mid - 1); i ++)
            if(g[i] + w(i, mid) < f[mid]) 
                f[mid] = g[i] + w(i, mid), pos = i;
        solve(l, mid - 1, L, pos);
        solve(mid + 1, r, pos, R);
    }

    复杂度的证明:分治树总共有$log$层,在每一层内两个指针把决策点树和被决策点树都扫了一遍

    第二种情况,决策点和被决策点互相影响

    即,被决策点在将来会成为决策点

    这时,我们可以采用二分 + 单调栈来优化,复杂度和分治同样,为$O(n log m + m)$

    我们可以用经常举的例子,一开始

    $1 sim n$对于$1 sim i$内的决策点在$i = 1$时

    一定长成这个样子:111111111111111111111111111111111111111111

    我们可以确定出$2$的最优取值,加入$2$

    这时,我们可以发现,$1 sim n$的决策点会更变为这个样子

    11111111111111111111222222222222222222222222

    这时,我们又可以确定出$3$的最优取值,然后就会变成

    11111111111111111111222222222222333333333333

    依次类推,每次寻找新的分界的过程可以二分

    特别的,以$3$为例,如果加入$3$后,变成了

    1111111111111111111133333333333333333333

    这时,我们需要弹掉$2$的决策区间,用栈可以维护

    也就是说,用栈+二分来维护即可

    略微的比分治要难写一点

    //二分出当前可以转移到哪些点
    int find(int x) {
        int l = lp[top], r = n;
        while(l <= r) {
            int mid = (l + r) >> 1;
            if(w(x, mid) < w(go[top], mid)) r = mid - 1;
            else l = mid + 1;
        }
        return l;
    }
    
    void solve() {
        int tra = top = 1;
        lp[1] = 1; go[1] = 0;
        //lp : 当前栈元素的转移区间的左端点
        //go : 当前栈元素的转移区间由谁来转移
        for(ri i = 1; i <= n; i ++) {
            if(i == lp[tra + 1]) tra ++;
            dp[i] = w(go[tra], i);
            while(w(i, lp[top]) < w(go[top], lp[top])) top --;
            int tmp = find(i);
            if(tmp <= n) lp[++ top] = tmp, go[top] = i;
        }
    }

    决策单调性的题目并不多,写几道就差不多知道套路了

    而且你知道了是决策单调性就简单了不少

    习题

    还是给出几道题目供大家练手吧...

    $[HNOI2008]$  玩具装箱

    $[NOI2009]$  诗人小G

    $51nod1789$  跑的比谁都快

    $bzoj5125$  小Q的书架

    $CF868F$  Yet Another Minimization Problem 

    $51nod1488$  帕斯卡小三角(存在决策单调性)

    一些有趣

  • 相关阅读:
    [数据结构]线性表
    对C语言中指针的一些新认识
    Qt做动画旋转旋转图片
    延时程序执行Qt
    关于部分网页打不可的问题
    关于QString中的arg()函数使用方法
    Qt5.3.0 for Android开发环境配置
    QMenu,contextmenuevent,窗体透明
    Qt自定义窗体,边框,圆角窗体
    一个良好的团队
  • 原文地址:https://www.cnblogs.com/reverymoon/p/9872826.html
Copyright © 2020-2023  润新知