• (用POJ 1160puls来讲解)四边形不等式——区间dp的常见优化方法


      本文为深度解析理解四边形不等式,定义、优化和证明的主体部分均摘自 百度百科,加粗的补充部分才是干货精华;初学入门请移步https://blog.csdn.net/noiau/article/details/72514812(超详细入门+合并石子入门问题全解析)

    定义

      如果对于任意的a1≤a2<b1≤b2,有m[a1,b1]+m[a2,b2] ≤ m[a1,b2]+m[a2,b1](交叉区间之和不大于包含区间之和,取等号时也成立),那么m[i,j]满足四边形不等式。
     

    优化

      设m[i,j]表示动态规划的状态量。m[i,j]有类似如下的状态转移方程:m[i,j]=min{ m[i,k]+m[k,j] }  (i≤k≤j) (区间[i,j]的量是对所有的k∈[i,j],区间[i,k]与[k+1,j]的量之和的最小值。最大值也是同理可证,写为[i,k]和[k,j]是为了证明时方便,其实本质一样)。 m满足四边形不等式是适用下面优化方法的必要条件。
     
      对于一道具体的题目,我们首先要证明它满足这个条件,一般来说用数学归纳法证明。根据题目的不同而不同,
    通常的动态规划的复杂度是O(n^3),(即for一遍i,再for一遍j,还得for一遍k),我们可以优化到O(n^2),(即最内层对k的for循环被优化为 for一遍j的全过程只遍历一次)
     
     
      具体:定义s(i,j)为函数m(i,j)对应的使得m(i,j)取得最小值的k值。我们可以证明,s[i,j-1]≤s[i,j]≤s[i+1,j]  (本质是s[i,j]对 i 非递减,对 j 非递减,s[i-1,j]≤s[i,j]≤s[i,j+1]也成立)
    那么改变状态转移方程为:
        m[i,j] = min{m[i,k]+m[k,j]} (s[i,j-1]≤k≤s[i+1,j])  (最内层对k的for循环的边界条件改变)
     
      复杂度分析:不难看出,复杂度决定于s的值,以求m[i,i+L]为例,
        (s[2,L+1]-s[1,L])+(s[3,L+2]-s[2,L+1])…+(s[n-L+1,n]-s[n-L,n-1])=s[n-L+1,n]-s[1,L] ≤ n
    所以总复杂度是O(n),即最内层for循环的嵌套相当于与中间层for循环并列,内两层for循环的总复杂度相当于并列的两次for循环的复杂度O(2n)
     

    证明

    对s[i,j-1]≤s[i,j]≤s[i+1,j]的证明:(不用太纠结具体证明过程,结合实际题目理解结论所代表的意思即可)
     
    设mk[i,j]=m[i,k]+m[k,j],s[i,j]=d (即mk[i,j]记号的意义,使m[i,j]取最小值的k值为d)
    对于任意k<d,有mk[i,j]≥md[i,j](这里以m[i,j]=min{m[i,k]+m[k,j]}为例,max的类似),接下来只要证明mk[i+1,j]≥md[i+1,j],那么只有当s[i+1,j]≥s[i,j]时才有可能有mk[i+1,j]≥md[i+1,j]
    (mk[i+1,j]-md[i+1,j])-(mk[i,j]-md[i,j])
    =(mk[i+1,j]+md[i,j])-(md[i+1,j]+mk[i,j])
    =(m[i+1,k]+m[k,j]+m[i,d]+m[d,j])-(m[i+1,d]+m[d,j]+m[i,k]+m[k,j])
    =(m[i+1,k]+m[i,d])-(m[i+1,d]+m[i,k])
    ∵m满足四边形不等式,∴对于i<i+1≤k<d有m[i+1,k]+m[i,d]≥m[i+1,d]+m[i,k]
    ∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0
    ∴s[i,j]≤s[i+1,j],同理可证s[i,j-1]≤s[i,j]
    证毕
     

    例题讲解

    题面(POJ 1160的增强版)

      在n个居民点建立m个邮局,每个居民点有一个一维坐标,记第i个居民点到最近邮局的距离为dis[i],求i从1到n,Σdis[i] 的总和的最小值。
     
    输入:第一行输入n,m(n,m∈[1,3000],m<=n),第二行输入n个居民点的坐标(坐标在int范围内)。
    6 2
    1 2 3 4 5 1000
     
    输出:所求的最近距离总和的最小值。
    6
    //即:在1000位置建一个邮局,在3位置再建立一个邮局时,最近距离总和为2+1+0+1+2+0 = 6。
     

    dp公式的建立

      1. n个居民点,m个邮局,想到用dp[i][j]代表建i个邮局时,(排序后)前j个居民点的最近距离总和的最小值。

      2. 思考递推关系,dp[i][j]可由dp[i-1][k]和居民点k+1到j之间建立一个新邮局的情况 推出 (k<j)。

    则定义d[i][j] = 在居民点i到j上只建立一个邮局时的居民点i到j 的最近距离总和的最小值。

      3. 则dp[i][j] = min(dp[i-1][k] + d[k+1][j]),1<=k<j。

      4. d数组的求法,d[i][j]中存在递推关系:d[i][j] = d[i][j-1] + (a[j]-a[(i+j)/2])(即新增加一个居民点j后,邮局一定在i,j的中点(取下整)位置,原来i到j-1个居民点的最近距离总和不变)

    注意:此处存在一个疑惑,在k+1到j之间建立新邮局后,居民点1到k的最近距离是否改变?

      答案是:此公式成立时,居民点1到k的最近距离不会改变。

      因为若存在[p,k]的居民点到新邮局的距离比之前的更近,则dp[i-1][p-1] + d[p][j]一定比dp[i-1][k] + d[k+1][j] 更小。

    所以此最小值成立时,所有居民点的最近距离不变。(即[1,k]的居民点都离前面的邮局更近,而[k+1,j]的居民点都离后面的新邮局更近)

    四边形不等式优化

      明显,求d[i][j]需要两层for循环,而求dp[i][j]时需要对i,j,k进行三层循环,复杂度O(n^3),需要进行优化。

      可以想到此dp公式包含着d[1][k] + d[k+1][j]的一个子区间之和最小值问题,那么dp[i][j] = min(dp[i-1][k] + d[k+1][j]),1<=k<j 这个复合的地推公式的决策点s[i][j]是否也可以用四边形不等式优化呢?(决策点s(i,j)为使得dp(i,j)取得最小值的k值。)

      严格的数学证明需要先证明d数组满足d[i][j]+d[i+1][j+1] <= d[i][j+1]+d[i+1][j](i<=i+1<=j<=j+1)的四边形不等式条件,再证明dp数组也满足此条件,再证明验证s[i,j]对 i 非递减,对 j 非递减。

      实际上d[i+1][j+1] - d[i+1][j] = a[j+1] - a[(i+j+2)/2],d[i][j+1] - d[i][j] = a[j+1] - a[(i+j+1)/2],而a[(i+j+2)/2] >= a[(i+j+1)/2]成立,所以d[i][j]满足四边形不等式条件。

    再后面的证明其实无需进行,在实际做题时直接打印s[i][j]验证结论成立即可。

      这样  dp[i][j] = min(dp[i-1][k] + d[k+1][j]) (s[i,j-1]≤k≤s[i+1,j])

    优化后的伪代码

      1. 先对居民点坐标数组a进行排序,然后依据递推关系:d[i][j] = d[i][j-1] + (a[j]-a[(i+j)/2]),双for得出d数组。

      2. dp[1][j]其实就是d[1][j]。

      3. 因为在求dp[i][j]时要使用 s[i,j-1]≤k≤s[i+1,j] 进行优化,那么 j 应该从小到大递推,i 应该从大到小递推,以使用上一次的 j-1和 i+1。

    但是dp的递推关系只能是邮局数 i 从小到大递推,所以应该使用 s[i-1,j]≤k≤s[i,j+1]进行优化(本质是决策点s[i,j]对 i 非递减,对 j 非递减)

    那么 i 应该从小到大递推,j应该从大到小递推。dp[i][j] = min(dp[i-1][k] + d[k+1][j]) (s[i-1,j]≤k≤s[i,j+1])

      4.所以可以先for i in [2,m],再for j in [n,i+1],最后for k in [s[i-1][j], s[i][j+1]],递推出dp[i][j]和s[i][j]。 注意设置对i in [2,m]所有s[i][n+1]的值都为n-1,因为j=n时k最大取n-1。

    代码

    #include<cstdio>
    #include<iostream>
    using namespace std;
    
    int a[3003], d[3003][3003], f[3003][3003], s[3003][3003];
    int main() {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i < n; i++)
            cin >> a[i];
        sort(a, a + n);
        for (int i = 0; i < n; i++) {
            //d[i][i] = 0;
            for (int j = i + 1; j < n; j++)
                d[i][j] = d[i][j - 1] + a[j] - a[(i + j) / 2];
            f[0][i] = d[0][i];    //f[0][i]表示在前i+1个节点中建0+1个站时的最近距离总和的最小值。
        }
    
        for (int i = 1; i < m; i++)s[i][n] = n - 2;    //准备s[i][n]的初值,s[0][j]的初值直接使用默认值0
        for (int i = 1; i < m; i++) {
            //for (int j = 0; j <= i; j++)f[i][j] = 0;
            //为了使用s[i][j]的结论,j从后往前递推
            for (int j = n - 1; j > i; j--) {
                f[i][j] = 0x7fffffff;
                //使用s[i][j]的结论:因为i在最外层,不能由之前得到s[i+1][j],所以变通为s[i-1][j]。
                //这样就会需要使用s[i][j+1],需要由之前得到s[i][j+1],所以j从后往前递推
                for (int k = s[i - 1][j]; k <= s[i][j + 1]; k++) {
                    if (f[i - 1][k] + d[k + 1][j] < f[i][j]) {
                        f[i][j] = f[i - 1][k] + d[k + 1][j];
                        s[i][j] = k;
                    }
    
                }
            }
        }
        cout << f[m - 1][n - 1];
        return 0;
    }
    View Code

    总结

      其实,很多状态转移方程都满足四边形不等式优化的条件。m[i,j]不一定非得代表区间[i,j],只要转移方程中隐含一个两子区间量之和的最值问题,任何需要2维dp数组记录的dp量都可以尝试用四边形不等式进行优化。
     
    解决这类问题的大概步骤是:
    1. 证明w满足四边形不等式,这里w是m的附属量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此时大多要先证明w满足条件才能进一步证明m满足条件
    2. 证明m满足四边形不等式。(其实证明1之后,基本就可以直接验证决策点s[i][j]是否满足对 i 和 j 都非递减)
    3. 证明和验证s[i,j-1]≤s[i,j]≤s[i+1,j]
    4. 使用合适的遍历顺序,利用3.的结论进行优化。
     
  • 相关阅读:
    Doing Homework 简单dp&&状态压缩
    嫖裤子序列
    王宁宁宁
    友军寻路法
    Viviani
    ccf 201909-3
    ccf 201909-5
    链式前向星
    ccf-201909-04
    ccf -201909-2
  • 原文地址:https://www.cnblogs.com/zsh-notes/p/12581194.html
Copyright © 2020-2023  润新知