zoukankan      html  css  js  c++  java
  • 区间DP与贪心算法的联系(uav Cutting Sticks && poj Fence Repair(堆的手工实现))

      由于,这两题有着似乎一样的解法所以将其放在一起总结比較,以达到更好的区分二者的差别所在。


    一、区间DP


    uva的Cutting Sticks是一道典型的模板题。

    题目描写叙述: 

      有一根长度为l的木棍,木棍上面有m个分割点,每一次分割都要付出当前木棍长度的代价,问如何分割有最小代价。

    区间DP的定义:

       区间动态规划问题一般都是考虑。对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用。将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合,求合并后的最优值。

    解法:
       设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价 。 最小区间F[i,i]=0(一个数字无法合并,∴代价为0)每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段

    区间DP模板,代码:

    for(intp = 1 ; p <= n ; p++){      //p是区间的长度,作为阶段
      for(int i = 1 ; i <= n ; i++){   //i是穷举区间的起点
          int j = i+p-1;               //j为区间的终点
          for(int k = i ; k < j ; k++)//状态转移
             dp[i][j] = min{dp[i][k]+dp[k+1][j]+w[i][j]};//这个是看题目意思,有的是要从k開始不是k+1
             dp[i][j]= max{dp[i][k]+dp[k+1][j]+w[i][j]};
          }
    }
    


    改题解法:
       对于这一题,假设我们仅仅对最左边的分割点到最右边的分割点进行DP。那么得到的答案肯定是错的。由于不是整个区间。所以我们必须在木棍的做左边和左右边分别添加一个点,那么得到的就是整个区间。再对这个区间进行DP求解就可以。


    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int MAXN = 50 + 10;
    const int INF = ~0U >> 2;
    int w[MAXN],dp[MAXN][MAXN];
    int L,n;
    
    int solve(){
        n++;
        for(int i = 0;i <= n;++i)
            for(int j = i+1;j <= n;++j)
                dp[i][j] = (i+1 == j ? 0 : INF);
    
        w[0] = 0; w[n] = L;
        for(int p = 1;p <= n;++p){            //区间长度
            for(int s = 0;s <= n-p;++s){       // 起始位置
                int e = s + p;                //终点
                for(int k = s+1;k < e;++k){    //状态转移
                    dp[s][e] = min(dp[s][e],dp[s][k] + dp[k][e] + w[e] - w[s]);
                }
            }
        }
        return dp[0][n];
    }
    int main()
    {
        while(scanf("%d",&L),L){
            scanf("%d",&n);
            for(int i = 1; i <= n; ++i){
                scanf("%d",&w[i]);
            }
            printf("The minimum cutting is %d.
    ",solve());
        }
        return 0;
    }
    


     

    POJ的这题呢,能够看作是上体的上级版。

        是没有给出固定的位置的,木板的分割顺序不确定,自由度非常高。这题貌似非常难入手。

    可是事实上能够用稍微奇特的贪心来求解。


    原理解析:

        过程分析是一个涉及了哈夫曼编码的二叉树,由于分析过程有点下复杂。所以自己联想一下吧(囧)。以下给出的算法是不断求解huffman编码中的最小值和次小值。因此,朴素的算法是O(N*N).我们也能够想到用到二叉树结构的优先队列来求解最小值和次小值。此时的时间复杂度减为了O(NlogN).

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    typedef long long LL;
    const int MAXN = 20000 + 10;
    
    int N,L[MAXN];
    
    LL solve(){
       LL ans = 0;
       
       //直到计算到一块木板时为止
       while(N > 1){
         //寻找最小值,次小值
         int mii1 = 0,mii2 = 1;
         if(L[mii1] > L[mii2]) swap(mii1,mii2);
         for(int i = 2; i < N; ++i){
            if(L[i] < L[mii1]){
                mii2 = mii1;
                mii1 = i;
            }
            else if(L[i] < L[mii2]){
                mii2 = i;
            }
         }
         int t = L[mii1] + L[mii2];           //合并
         ans += t;
    
         if(mii1 == N-1) swap(mii1,mii2);
         L[mii1] = t;
         L[mii2] = L[N-1];
         N--;
       }
       return ans;
    }
    int main()
    {
        scanf("%d",&N);
        for(int i = 0;i < N;++i){
            scanf("%d",&L[i]);
        }
        printf("%lld
    ",solve());
        return 0;
    }
    

    使用STL中的priority_queue实现。

    时间复杂度为O(N*logN).


    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    typedef long long LL;
    const int MAXN = 20000 + 10;
    
    int N,L[MAXN];
    
    LL solve(){
       LL ans = 0;
    
       priority_queue<int,vector<int>,greater<int> > que;
       for(int i = 0;i < N;++i){
           que.push(L[i]);
       }
    
       while(que.size() > 1){
           int l1,l2;
           l1 = que.top();
           que.pop();
           l2 = que.top();
           que.pop();
    
           ans += l1 + l2;
           que.push(l1+l2);
       }
       return ans;
    }
    int main()
    {
        scanf("%d",&N);
        for(int i = 0;i < N;++i){
            scanf("%d",&L[i]);
        }
        printf("%lld
    ",solve());
        return 0;
    }
    

    使用手写堆实现。时间复杂度O(N*logN)。


    堆实现模板:

    //堆的实现
    int heap[MAXN << 2],sz = 0 ;
    
    void push(int x){
       //自己结点的编号
       int i = sz++;
       while(i > 0){
          //父亲结点的编号
          int p = (i - 1) >> 1;
    
          //假设已经没有大小颠倒则退出
          if(heap[p] <= x)break;
    
          //把父亲结点的数值放下来。而把自己提上去
          heap[i] = heap[p];
          i = p;
       }
       heap[i] = x;
    }
    
    int pop(){
       //最小值
       int ret = heap[0];
    
       //要提到根的数值
       int x = heap[--sz];
    
       //从根開始向下交换
       int i = 0;
       while((i << 1 | 1) < sz){
          //比較儿子
          int a = i << 1 | 1,b = (i << 1) + 2;
          if(b < sz && heap[b] < heap[a]) a = b;
    
          // 假设没有大小颠倒则退出
          if(heap[a] >= x) break;
    
          //把儿子提上来
          heap[i] = heap[a];
          i = a;
       }
    
       heap[i] = x;
       return ret;
    }




    
  • 相关阅读:
    团队项目-选题报告
    1
    第二次结对编程作业
    第2组 团队展示
    第02组 Alpha冲刺(4/6)
    第02组 Alpha冲刺(3/6)
    第02组 Alpha冲刺(2/6)
    第02组 Alpha冲刺(1/6)
    第02组 团队GIT现场编程实战
    团队项目-需求分析报告
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7222184.html
Copyright © 2011-2022 走看看