• 5488: 石子归并II (区间DP+环形DP+四边形不等式优化)


    四边形不等式优化_石子合并问题_C++

     

      在动态规划中,经常遇到形如下式的状态转移方程:

        m(i,j)=min{m(i,k-1),m(k,j)}+w(i,j)(i≤k≤j)(min也可以改为max)

      上述的m(i,j)表示区间[i,j]上的某个最优值。w(i,j)表示在转移时需要额外付出的代价。该方程的时间复杂度为O(N3)

       

      下面我们通过四边形不等式来优化上述方程,首先介绍什么是“区间包含的单调性”和“四边形不等式”

        1、区间包含的单调性:如果对于 i≤i'<j≤j',有 w(i',j)≤w(i,j'),那么说明w具有区间包含的单调性。(可以形象理解为如果小区间包含于大区间中,那么小区间的w值不超过大区间的w值)

        2、四边形不等式:如果对于 i≤i'<j≤j',有 w(i,j)+w(i',j')≤w(i',j)+w(i,j'),我们称函数w满足四边形不等式。(可以形象理解为两个交错区间的w的和不超过小区间与大区间的w的和)

      下面给出两个定理:

        1、如果上述的 w 函数同时满足区间包含单调性和四边形不等式性质,那么函数 m 也满足四边形不等式性质

           我们再定义 s(i,j) 表示 m(i,j) 取得最优值时对应的下标(即 i≤k≤j 时,k 处的 w 值最大,则 s(i,j)=k)。此时有如下定理

        2、假如 m(i,j) 满足四边形不等式,那么 s(i,j) 单调,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)。

      好了,有了上述的两个定理后,我们发现如果w函数满足区间包含单调性和四边形不等式性质,那么有 s(i,j-1)≤s(i,j)≤s(i+1,j) 。

      即原来的状态转移方程可以改写为下式:

         m(i,j)=min{m(i,k-1),m(k,j)}+w(i,j)(s(i,j-1)≤k≤s(i+1,j))(min也可以改为max)

      由于这个状态转移方程枚举的是区间长度 L=j-i,而 s(i,j-1) 和 s(i+1,j) 的长度为 L-1,是之前已经计算过的,可以直接调用。

      不仅如此,区间的长度最多有n个,对于固定的长度 L,不同的状态也有 n 个,故时间复杂度为 O(N^2),而原来的时间复杂度为 O(N^3),实现了优化!

      今后只需要根据方程的形式以及 w 函数是否满足两条性质即可考虑使用四边形不等式来优化了。

      以上描述状态用 m(i,j),后文用的 dp[i][j],所代表含意是相同的,特此说明。

      以石子合并问题为例。

      例如有6堆石子,每堆石子数依次为3 4 6 5 4 2

      因为是相邻石子合并,所以不能用贪心(每次取最小的两堆合并),只能用动归。(注意:环形石子的话,必须要考虑最后一堆和第一堆的合并。)

      例如:一个合并石子的方案:

        第一次合并 3 4 6 5 4 2 ->7

        第二次合并 7 6 5 4 2 ->13

        第三次合并 13 5 4 2 ->6

        第四次合并 13 5 6 ->11

        第五次合并 13 11 ->24

      总得分=7+6+11+13+24=61 显然,比贪心法得出的合并方案(得分:62)更优。

      

      动归分析类似矩阵连乘等问题,得出递推方程:

        设 dp[i][j] 表示第 i 到第 j 堆石子合并的最优值,sum[i][j] 表示第 i 到第 j 堆石子的总数量。

        (可以在计算开始先做一遍求所有的 sum[i],表示求出所有第1堆到第i堆的总数量。则 sum[i][j]=sum[j]-sum[i]。这样计算比较快。)

      那么就有状态转移公式:

          

        这里 i<=k<j

      普通解法需要 O(n^3)。下面使用四边形不等式进行优化。

      首先判断是否符合区间单调性和四边形不等式。

         i  i'    j    j'

        3 4 6 5 4 2

      单调性:

        w[i',j] = 4+6+5=15 w[i,j'] =3+4+6+5+4+2=24

      故w[i',j] <= w[i,j'] 满足单调性

      四边形不等式:

        w[i,j] + w[i',j'] = (3+4+6+5) + (4+6+5+4+2) = 18+21 = 39

        w[i',j] + w[i,j'] = (4+6+5) + (3+4+6+5+4+2) = 15 + 24 = 39

        故 w[i,j] + w[i',j'] <= w[i',j] + w[i,j']

      故石子合并可利用四边形不等式进行优化。

      利用四边形不等式,将原递推方程的状态转移数量进行压缩(即缩小了k的取值范围)。

      令 s[i][j]=min{k | dp[i][j] = dp[i][k-1] + dp[k][j] + w[i][j]},即计算出 dp[i][j] 时的最优的 k 值(本例中寻优为取最小)

      也可以称为最优决策时的 k 值。由于决策 s 具有单调性,因此状态转移方程中的 k 的取值范围可修改为 :

        s[i,j-1] <= s[i,j] <= s[i+1,j]

        边界:s[i,i] = i

      因为 s[i,j] 的值在 m[i,j] 取得最优值时,保存和更新,因此 s[i,j-1] 和 s[i+1,j] 都在计算 dp[i][j-1] 以及 dp[i+1][j] 的时候已经计算出来了。

      因此,s[i][j] 即 k 的取值范围很容易确定。

    描述

     

    N堆石子摆成一个环。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

     

    例如: 1 2 3 4,有不少合并方法

    1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)

    1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)

    1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)

     

    括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。

    输入

     

    第1行:N(2 <= N <= 1000)
    第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)

    输出

     

    输出最小合并代价。

    样例输入

    样例输出

     1 #include <bits/stdc++.h>
     2 #define ll long long
     3 using namespace std;
     4 
     5 const int maxn=2e3+5;
     6 const ll INF=0x3f3f3f3f3f3f3f3f;
     7 int arr[2*maxn];
     8 int n;
     9 ll dp[maxn][maxn],pos[maxn][maxn];
    10 ll sum[maxn];
    11 
    12 void init(){
    13     memset(dp,INF,sizeof(dp));
    14     for(int i=1;i<=2*n;i++){
    15         dp[i][i]=0;
    16         pos[i][i]=i;
    17     }
    18 }
    19 
    20 int main(){
    21     ios::sync_with_stdio(false);
    22     cin>>n;
    23     for(int i=1;i<=n;i++) cin>>arr[i],arr[n+i]=arr[i];
    24     for(int i=1;i<=2*n;i++) sum[i]=sum[i-1]+arr[i];
    25     init();
    26     for(int len=1;len<=n;len++)
    27     for(int i=1;i+len<=2*n;i++){
    28         int j=i+len;
    29         for(int k=pos[i][j-1];k<=pos[i+1][j];k++){
    30             if(dp[i][j]>dp[i][k]+dp[k+1][j]){
    31                 pos[i][j]=k;
    32                 dp[i][j]=dp[i][k]+dp[k+1][j];
    33             }
    34         }
    35         dp[i][j]+=sum[j]-sum[i-1];
    36     }
    37     ll minn=INF;
    38     for(int i=1;i<=n;i++){
    39         minn=min(minn,dp[i][i+n-1]);
    40     }
    41     cout << minn << endl;
    42     return 0;
    43 }
    View Code
  • 相关阅读:
    在Linux下安装和使用MySQL
    vc动态装载动态库
    stl学习(转帖2)
    makefile
    详细的MySQL C API
    Excel INTO SQLSERVER
    Outlook2010中预览Word,Excel附件问题
    11gRAC ASM管理的数据文件丢失恢复
    ASM上控制文件损坏时的恢复
    使用NET USER增加一个超级管理 & 激活Windows 7 administrator
  • 原文地址:https://www.cnblogs.com/qq-1585047819/p/11771153.html
Copyright © 2020-2023  润新知