XUANMING'S JOURNAL屏幕在深夜微微发亮,我心在远方。
← 回到笔记
FIELD NOTES

四边形不等式优化DP小结

这篇笔记的路线图

定义

四边形不等式用于优化形如以下类型dp:

$$ f(i) = \min_{1 \leq j \leq i} w(j,i) \qquad \left(1 \leq i \leq n\right) \tag{1} $$

其中$w(j,i)$满足:

$$ 若a\le b\le c\le d\\ 有w(a,c)+w(b,d)\le w(a,d)+w(b,c) $$

若满足以上条件,约定$opt(i)$表示对于$i$来说最优决策点$j$的位置,则有:

$$ i_1\le i_2 \Rightarrow opt(i_1)\le opt(i_2) $$

以上性质称为决策单调性。

两种基本方法

分治法

要求解所有状态,只需要求解所有最优决策点。为了对所有 $1 \leq i \leq n$ 求解 $\mathop{\mathrm{opt}}(i)$,首先计算 $\mathop{\mathrm{opt}}(n/2)$,而后分别计算 $1 \leq i < n/2$ 和 $n/2 < i \leq n$ 上的 $\mathop{\mathrm{opt}}(i)$,注意此时已知前半段的 $\mathop{\mathrm{opt}}(i)$ 必然位于 $1$ 和 $\mathop{\mathrm{opt}}(n/2)$ 之间(含端点),而后半段的 $\mathop{\mathrm{opt}}(i)$ 必然位于 $\mathop{\mathrm{opt}}(n/2)$ 和 $\mathop{\mathrm{opt}}(n)$ 之间(含端点)。对于两个子区间,也类似处理,直至计算出每个问题的最优决策。在分治的过程中记录搜索的上下边界,就可以保证算法复杂度控制在 $O(n\log n)$。递归树层数为 $O(\log n)$,而每层中,单个决策点至多计算两次,所以总的计算次数是 $O(n\log n)$。

int w(int j, int i);
void DP(int l, int r, int k_l, int k_r) {
    int mid = (l + r) / 2, k = k_l;
    // 求状态f[mid]的最优决策点
    for (int j = k_l; j <= min(k_r, mid - 1); ++j)
    if (w(j, mid) < w(k, mid)) k = j;
    f[mid] = w(k, mid);
    // 根据决策单调性得出左右两部分的决策区间,递归处理
    if (l < mid) DP(l, mid - 1, k_l, k);
    if (r > mid) DP(mid + 1, r, k, k_r);
}

二分队列

注意到对于每个决策点 $j$,能使其成为最小最优决策点的问题 $i$ 必然构成一个区间。可以通过单调队列记录到目前为止每个决策点可以解决的问题的区间,这样,问题的最优解自然可以通过队列中记录的决策点计算得到。算法大致如下。

int val(int j, int i);
int lt[N], rt[N], f[N];
deque<int> dq;
// 初始化队列
dq.emplace_back(1);
lt[1] = 1;
rt[n] = n;
// 顺次考虑所有问题和决策
for (int j = 1; j <= n; ++j) {
    // 出队
    while (!dq.empty() && rt[dq.front()] < j) {
        dq.pop_front();
    }
    // 计算
    f[j] = val(dq.front(), j);
    // 入队
    while (!dq.empty() && val(j, lt[dq.back()]) < val(dq.back(), lt[dq.back()])) {
        dq.pop_back();
    }
    if (dq.empty()) {
        dq.emplace_back(j);
        lt[j] = j + 1;
        rt[j] = n;
    } else if (val(j, rt[dq.back()]) < val(dq.back(), rt[dq.back()])) {
        if (rt[dq.back()] < n) {
            dq.emplace_back(j);
            lt[j] = rt[dq.back()] + 1;
            rt[j] = n;
        }
    } else {
        int ll = lt[dq.back()];
        int rr = rt[dq.back()];
        int i;
        // 二分
        while (ll <= rr) {
            int mm = (ll + rr) / 2;
            if (val(j, mm) < val(dq.back(), mm)) {
                i = mm;
                rr = mm - 1;
            } else {
                ll = mm + 1;
            }
        }
        rt[dq.back()] = i - 1;
        dq.emplace_back(j);
        lt[j] = i;
        rt[j] = n;
    }
}

掌握这一算法,需要理解如下要点:

  • 队列需要记录到目前为止每个可行的决策点 $j$ 和能够解决的问题区间左右端点 $l_j$ 和 $r_j$ 构成的 三元组。对于给定区间 $[l_j,r_j]$ 内的问题,$j$ 应该是到目前为止考虑过的决策点中最小最优的(以下简称最优决策)。每时每刻,队列中存储的决策未必是连续的,但是尚未解决的问题应该是队列中存储的问题区间的不交并。
  • 初始化:将首个决策放于队列中,并记录它对于所有问题都是最优的。
  • 类似于单调队列,每次考虑下一个决策 $j$ 的时候,都需要进行出队和入队操作。
  • 出队:当所有决策 $j \leq i$ 都考虑结束后,问题 $i$ 的解就是队列中首个满足 $l_j \leq i \leq r_j$ 的决策点 $j$。此时可以弹出所有满足 $r_j < i$ 的队首。由于决策单调性,弹出的决策也不会是后续问题的最优决策。
  • 入队:要对决策 $j$ 进行入队时,首先比较它和队尾的决策 $j'$。
    • 如果对于问题 $l_{j'}$,将入队的决策 $j$ 比已有的决策 $j'$ 更优,即 $w(j,l_{j'}) < w(j',l_{j'})$ 时,则弹出队尾的决策 $j'$。此操作持续到队尾的决策 $j'$ 比起 $j$ 对于问题 $l_{j'}$ 更优时为止。
    • 如果队列已空,入队 $(j,j+1,n)$,即认为决策 $j$ 是尚未解决的所有问题的最优解。
    • 如果队尾决策 $j'$ 对于问题 $r_{j'}$ 同样优于将入队的决策 $j$,那么当 $r_{j'} < n$ 时,入队 $(j,r_{j'}+1,n)$,表示 $j$ 是对于问题 $[r_{j'}+1,n]$ 的最优解,否则,不需要入队 $j$,因为它并不比已有的决策更优。
    • 最后的情形是,队尾决策 $j'$ 比起要入队的决策 $j$ 对于问题 $l_{j'}$ 更优,而对于问题 $r_{j'}$ 更劣,那么,需要通过 二分 找到最小的 $i\in[l_{j'},r_{j'}]$ 使得 $w(j,i) < w(j',i)$,将队尾的区间右端点修改为 $i-1$,并入队 $(j,i,n)$。

两种基本问题

区间分拆问题

考虑将某个区间拆分成若干个子区间的问题。形式化地说,将给定区间 $[1,n]$ 拆分成 $[a_1,b_1],\cdots,[a_k,b_k]$,其中,$b_1=1$,$a_k=n$,以及 $b_{i}+1=a_{i+1}$ 对任意 $i < k$ 都成立。对于给定拆分,成本为 $\sum_{i=1}^kw(a_i,b_i)$。问题要求最小化这一成本。可以列出如下的 1D1D 状态转移方程。

$$ f(i) = \min_{1\leq j\leq i} f(j-1)+w(j,i) \qquad (1\leq i\leq n) $$

这里,$f(0)=0$。注意到,只要 $w(j,i)$ 满足四边形不等式,$f(j-1)+w(j,i)$ 必然满足四边形不等式,因为第一项并不包括 $j$ 和 $i$ 的交叉项,在混合差分时会消去。但是由于成本函数依赖于前面的子问题,这一转移只能够顺序计算,所以通常只适合应用二分队列算法。算法复杂度为 $O(n\log n)$。

上述问题可以加强为限制区间个数的情形,即问题指定将区间拆分成 $m$ 个子区间。此时需要将拆分后的区间个数作为转移状态的一维。相应地,有 2D1D 状态转移方程如下。

$$ f(k,i) = \min_{1\leq j\leq i} f(k-1,j-1)+w(j,i) \qquad (1\leq k\leq m,\ 1\leq i\leq n) \tag{2} $$

有以下引理:

若 $w$ 满足四边形不等式,则 $\mathop{\mathrm{opt}}(k-1,i) \leq \mathop{\mathrm{opt}}(k,i) \leq \mathop{\mathrm{opt}}(k,i+1)$。

利用这一结果,我们可以限制决策 $j$ 的搜索范围。算法实现时,对 $k$ 正向遍历,对 $i$ 逆向遍历,在之前已确定的上下界范围内暴力搜索 $j$ 就可以保证 $O(n(n+m))$ 的算法复杂度。

另有wqs二分可以优化到$O(n\log n\log C)$。

区间合并问题

另一类可以通过四边形不等式优化的动态规划问题是区间合并问题,即要将 $n$ 个长度为一的区间 $[i,i]$ 两两合并起来,直到得到区间 $[1,n]$。每次合并 $[j,k]$ 和 $[k+1,i]$ 时都需要支付成本 $w(j,i)$。问题要求找到成本最低的合并方式。对于此类问题,有如下 2D1D 状态转移方程。

$$ f(j,i) = \min_{j \leq k < i} f(j,k) + f(k+1,i) + w(j,i) \qquad (1\le j< i\le n) \tag{3} $$

这里给定任意初始成本 $f(i,i)=w(i,i)$。

可以证明$f(j,i)$满足四边形不等式,且有以下定理:

$$ \mathop{\mathrm{opt}}(j,i-1) \leq \mathop{\mathrm{opt}}(j,i) \leq \mathop{\mathrm{opt}}(j+1,i). \qquad (j + 1 < i) $$

利用这一结论,同样可以限制决策点 $k$ 的搜索范围。在这里,正序遍历区间长度 $i-j+1$,再遍历具有同样长度的所有区间 $[j,i]$,暴力搜索 $\mathop{\mathrm{opt}}(j,i-1)$ 和 $\mathop{\mathrm{opt}}(j+1,i)$ 之间的所有 $k$ 求得最优解 $f(j,i)$ 并记录最小最优决策 $\mathop{\mathrm{opt}}(j,i)$。总的算法复杂度为 $O(n^2)$ 。

参考资料

OI-Wiki 四边形不等式优化

输入关键词,搜索全部笔记。