区间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.\n",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\n",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\n",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;
}





  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值