区间DP值入门题目——石子归并

这两天特别丧,不想学习,心态有点崩。不知道为什么,可能是假期综合症吧,又感觉自己处在了一种极度的迷茫之中,都怪袁老,让我自己一天吃饱了没事干,胡思乱想,呜呜呜.jpg。

区间DP

 区间DP就是在区间上的动态规划,求解一段区间上的最优解,通过合并小区间的最优解来得到整个大区间上的最优解的算法。

时间复杂度一般为O(n^2)或者O(n^3),一般做法就是:

  1. 确定状态,例如初始化等操作。
  2. 枚举区间长度,枚举区间起始点,有的题目还需要枚举区间断点,从小区间到大区间,合并小区间来得到整个大区间上的最优解。
  3. dp[1][n]一般就是所求的解。

石子合并问题

很多人一看到石子合并问题可能会想到博弈论,但是博弈论上的一般是先手问题。而石子合并合并的一般是合并相邻两堆石子的数量。石子合并问题有两种玩法。操场玩法和路边玩法,操场玩法就是指把石子围成一堆,路边玩法就是把石子摆成一条直线。

贪心法不能解决,贪心法要求无后效性,但是条件为必须合并相邻两堆这个条件,贪心法无法保证每次都能取到所有堆中石子数最少的两堆。如6                 3  4  6  5  4  2贪心法的最小值是62,但是实际上最小值为61,很明显贪心算出来的并不是最小值,贪心法在子过程中得到的解只是局部最优,而不能保证全局的值最优,因此本问题不可以使用贪心法求解。如果使用暴力穷举的办法,会有大量的子问题重复,这种做法会造成效率的低下,所以我们可以考虑动态规划法,是否具有最优子结构性质。

(1)分析最优解的结构特征。

     假设已经知道了第k堆石子分开可以得到最优解,那么原问题就变成了两个子问题,子问题分别是{ai,a2,……,ak}和{ak+1,aj}原问题的最优解是否包含子问题的最优解呢?

   假设已经知道了n堆石子合并起来的花费是c,子问题1{ai,a2,……,ak}和{ak+1,……,aj}石子合并起来的花费是b,{a1,a2,……,aj}石子数量之和是w(i,j)那么c=a+b+w(i,j).因此我们只需要证明c是最优的,则a和b一定是最优的(即原问题的最优解包含子问题的最优解)。证明用反证法来证。

(2)建立最优值递归式。

扫描二维码关注公众号,回复: 5806023 查看本文章

   设dp[i][j]代表从第i堆到第j堆石子合并的最小花费,dp[i][k]代表从第i堆石子到第k堆石子合并的最小花费,dp[k+1][j]代表从第k+1堆石子到第j堆石子合并的最小花费。w(i,j)代表从第i堆到第j堆的石子数量之和。

                               dp[i][j]=0           i=j              //        min(dp[i][k]+dp[k+1][j]+w(i,j)   i<j (应该是大括号表示,但是不会用)

题目

石子归并

 51Nod - 1021 

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堆石子的数量,计算最小合并代价。



 

Input

第1行:N(2 <= N <= 100)

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

Output

输出最小合并代价

Sample Input

4
1
2
3
4

Sample Output

19
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=1<<30;
const int maxn=40001;
int Min[201][201];
//int Max[maxn][maxn];
int sum[maxn];
int a[maxn];
int main()
{
  int n;
  while(cin>>n&&n!=0)
  {
    memset(a,0,sizeof(a));
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;i++)
    {
      Min[i][i]=0;
    //  Max[i][i]=0;
    }
    for(int i=1;i<=n;i++)
    {
      cin>>a[i];
      sum[i]=sum[i-1]+a[i];
    }
    //c=a+b+w(i,j);w(i,j)代表价值和
    for(int v=2;v<=n;v++)
    {
      for(int i=1;i<=n-v+1;i++)
      {
        int j=i+v-1;
        Min[i][j]=INF;
        //Max[i][j]=-1;
        int tmp=sum[j]-sum[i-1];
        for(int k=i;k<j;k++)
        {
          Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+tmp);
        //  Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+tmp);
        }
      }
    }
    cout<<Min[1][n]<<endl;
    //cout<<Max[1][n]<<endl;
  }
  return 0;
}

这就是区间dp石子问题的常规解法,如果石子长度不长的话一般直接用dp来做,复杂度为O(n ^ 3),  但是这种方法时间复杂度并不低,在对时间有明显要求的时候这种方法不适合使用,这时候就要用到了一中新算法,叫(GarsiaWachs算法) 能把复杂度降到O(n ^ 2)甚至O(nlogn);
它的步骤如下:

设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[-1]和stone[n]是正无穷的。

举个例子:

假设石子的序列为100,45, 67, 200, 78; 那么第一次找的时候找到了67, 因为200 > 45,  然后合并为 112, 往前遍历,插到第一个比它大的值后面即stone[-1], 然后变成了112, 100, 200, 78,  继续操作, 然后合并112, 100为 212, 序列变为212 200 78,  记得stone[n]为无穷哦, 然后合并200, 78为278, 变为278, 212, 最后在合并为490

所以结果为112 + 212 + 278 + 490 = 1092。这种算法我还不是很会,呜呜呜,等我学会了再说。

圆型石子合并经常转换为直线型来求,也就是说,把圆形结构看成是长度为原规模两倍的直线结构来处理。如果操场玩法(直线玩法)原问题规模为n,所以就相当于有一排石子a1,a2,……,an-1.该问题规模为2*n-1,然后就可以用线性的石子合并问题来解决。

猜你喜欢

转载自blog.csdn.net/cjh1459463496/article/details/89047060