zoukankan      html  css  js  c++  java
  • pku 1160 Post Office 四边形不等式优化 经典DP

    pku 1160 Post Office 四边形不等式优化 经典DP

    邮局
    经典的动态规划问题,主要体现在状态的设计和可以用四边形不等式优化上
    题意是:给你n个村庄,然后让你用m个邮局对这些村庄进行覆盖,然后让你设计覆盖方式使得每个村庄到其对应邮局的路程和最短
    本题状态的设计的灵感来源于“覆盖”这个点,最优子结构其实就是用 m 个邮局覆盖,以及用 m-1个邮局覆盖
    那么,状态为dp[n][m] 为前 n 个村庄用 m 个邮局进行覆盖使得每个村庄到其对应的邮局的最短路程和
    转移方程:dp[n][m]=min{dp[k][m-1]+W[k+1,n]}  k>=m-1&&k<=n-1
    边界条件:dp[i][1]=W[1,i]  i>=1&&i<=n
    意为dp[n][m]这个状态可以由 前 k 个村庄用 m-1个邮局进行覆盖,k+1 到 n 这些村庄用第 m 个邮局进行覆盖所得的最短路程和转移得到
    W[k+1,n]是一个决策变量,表示 k+1 到 n 这些村庄中建立一个邮局所需的最短路程
    通过YY可以知道,n 个村庄如果只用一个邮局进行覆盖的话,那么该邮局建在村庄的中点位置可以使得总路程最短,即 x=[n/2] (方括号表示取整)
    那么W[k+1,n] 其实就是遍历一遍各个村庄到 x=[(k+1+n)/2] 这个点的距离,求和一下就行了
    分析一个算法复杂度,状态总数是O(n*m),状态转移需要O(n),那么总的时间复杂度是O(n*n*m)
    这题其实可以用四边形不等式做进一步的优化
     
    因为在动态规划中,有这样的一类问题
    状态转移方程 dp[i][j]=min{dp[i][k-1]+dp[k][j]}+w[i][j]  k>i&&k<=j  时间复杂度为 O(n*n*n)
    且有如下一些定义和定理:
    如果一个函数w[i][j],满足 w[i][j]+w[i'][j']<=w[i][j']+w[i'][j] i<=i'<=j<=j' 则称w满足凸四边形不等式
    如果一个函数w[i][j],满足 w[i'][j]<=w[i][j']  i<=i'<=j<=j' 则称w关于区间包含关系单调
     
    定理1:如果w同时满足四边形不等式和区间单调关系,则dp也满足四边形不等式
    定理2:如果定理1条件满足时让dp[i][j]取最小值的k为K[i][j],则K[i][j-1]<=K[i][j]<=K[i+1][j]
    注:定理2是四边形不等式优化的关键所在,它说明了决策具有单调性,然后我们可以据此来缩小决策枚举的区间,进行优化
    定理3:w为凸当且仅当 w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]
     
    几点说明:
    1:定理1的证明比较烦躁,详细的可以见《动态规划算法的优化技巧》毛子青 大神的论文
    2:定理3其实告诉我们验证w是否为凸的方法,就是固定一个变量,然后看成是一个一元函数,进而判断单调性。
    如,我们可以固定j算出w[i][j+1]-w[i][j]关于i的表达式,看它是关于i递增还是递减,如果是递减,则w为凸
    3:实际操作中,我们往往并不需要进行烦躁的证明,而只需要打表,然后观察就行了
    如w[i][j],dp[i][j]是否满足四边形不等式啊,w[i][j]是否单调啊,决策函数K[i][j]是否满足定理2的不等式关系啊,都可以通过打表来搞
     
    那么对于邮局这个问题,转移方程是dp[n][m]=min{dp[k][m-1]+W[k+1,n]}  k>=m-1&&k<=n-1
    很明显与上面的那种形式是类似的,因此我们考虑用四边形不等式进行优化
    且由于使用四边形不等式的时候,对于状态dp[i][j] 有个隐含条件是 i<j,因此需要把状态的位置倒一倒
    优化成 dp[n][m]=min{dp[k][m-1]+W[k+1,n]} 
    k=K[n][m]是一个最优间断点,也即一个最优决策点,意思是 前 m-1 个邮局所能覆盖的村庄数 
    那么有 K[n][m-1]<=k<=K[n+1][m]
    注:这里对k这个范围进行限制的时候可能会有一个小疑问,似乎K[n-1][m]<=k<=K[n][m+1]也是满足单调性的
    但是很遗憾的是,在这样的状态表示下,这样的区间限制是错的!!!
    原因在于要明白这里k具体的含义,k=K[n][m]记录的是在动态规划过程中,对应于状态dp[n][m]的一个最有决策,也即可以说dp[n][m]这个状态是由k这个决策而来,而通过上面的分析,我们可以知道决策具有单调性,因此上面计算出来的一些决策可以拿来对后面的状态转移进行决策区间的限制,那么这里很明显的有  K[n][m-1]<=k<=K[n+1][m] 这个式子成立,意思为 前 m-1 个邮局可以覆盖的村庄数 k,一定要大于等于前 m-2 个邮局可以覆盖的村庄数,所以左边那个不等式成立,而对于右边那个不等式其实只是根据单调性限定的一个上界,我们四边形不等式进行优化的本质其实是在左边那个不等式。其实也可以从动态规划计算的角度去分析,因为对于这样的状态和状态转移方程,我们计算状态的顺序是按照 m 从小到大来的(阶段是m),而四边形不等式优化的本质是通过决策的单调性,我们充分利用以前算出来的决策信息来使得下一阶段枚举决策的范围缩小,那么很明显的是 K[n][m-1]<=K[n][m] 这个不等式是显然必须成立的,是我们优化过程中成功的利用了以前的决策信息的体现。那么如果我们把状态的两个变量的位置调一下,那么 K[n-1][m]<=k<=K[n][m+1] 这样的区间限制就是正确的
    小结:k的四边形不等式区间限制,要根据具体k所对应于状态的含义,根据阶段计算的先后性,然后利用决策单调性来进行区间的限制,先是要满足含义上的表示,然后才是决策的单调性,单单满足决策单调性不一定是正确的
    代码如下:
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define inf 0x7ffffff
    #define maxn 310
    int dp[maxn][maxn];//dp[i][j]表示前i个村庄放j个邮局的最短距离
    int w[maxn][maxn];//w[i][j]表示[i,j]的最小距离
    int val[maxn];
    int s[maxn][maxn];//s[i][j]记录前j- 1个邮局的村庄数
    
    int main(){
        int n,m,i,j;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            for(i = 1 ; i <= n ; i ++) scanf("%d",&val[i]);
            for(int i = 1 ; i <= n ; i ++) //这里有一个递推公式可以进行预处理
            {   w[i][i] = 0;
                for(int j = i + 1 ; j <= n ; j ++) w[i][j] = w[i][j - 1] + val[j] - val[(j + i)/2];
            }
            for(i = 1 ; i <= n ; i ++)
            {   dp[i][1] = w[1][i];    s[i][1] = 0;
            }
            //for(int i=1;i<=m;i++) s[n+1][i]=n;
            for(i = 2 ; i <= m ; i ++)
            {
                s[n+1][i] = n ;//s[][]上限的初始化
                for(j = n ; j > i ; j --)
                 {
                    dp[j][i] = inf;
                    for(int k = s[j][i-1]; k <= s[j+1][i] ; k ++)
                    {
                        int tmp = dp[k][i-1] + w[k + 1][j];
                        if(tmp < dp[j][i])
                        {    dp[j][i] = tmp;    s[j][i] = k;
                        }
                    }
                }
            }
            printf("%d
    ",dp[n][m]);
        }
    }
    

      

  • 相关阅读:
    python35 1.守护进程 2.互斥锁3.IPC 4.生产者和消费者模型
    python34 多进程: 1.进程与程序的区别 2.阻塞 非阻塞 并行 并发(重点) 3.进程三种方式的切换 4.程序员永恒的话题 5.进程的创建以及销毁 6.进程的两种使用方式(重点) 7.join函数(重点) 8.process常用属性 9僵尸与孤儿进程(了解)
    Python33 1.UDP协议 2.与TCP区别 3.DNS服务器 4.进程----进入并发编程的学习 5.操作系统 6.多道技术
    Mybatis 属性配置
    SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession
    MyBatis 简单入门
    spring mvc 异步 DeferredResult
    Spring MVC 异步请求 Callable
    Servlet 异步处理
    @EnableWebMvc
  • 原文地址:https://www.cnblogs.com/boson-is-god/p/6224473.html
Copyright © 2011-2022 走看看