动态规划小结

做了几道关于动态规划的例题,小结一下,以便以后回顾(ps:code和例题基本上来自于《算法入门经典》,只不过加入了自己的一些理解,有错误的地方,请见谅)

暴力求解(回溯)—  剪枝  —   动态规划,逐步减少对于问题的搜索力度,减少不必要的搜索,达到优化的目的。

动态规划和分治法的区别

可以用动态规划进行求解的问题的一些特点:

Ø  最优子结构

Ø  子问题重叠

Ø  边界

Ø  子问题独立

状态转移方程是动态规划问题的关键。

在实现动态规划的时候,通常采用用数组记录已经求得的子问题的结果,当再次访问到该节点时可以通过访问数组元素得到;在求解问题时通常会采用倒着求解的方法,当然也可以正着求解了。

通过例题加深题解:

数字三角形问题:有一个非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。从第一行的数开始,每次可以往左下和右下各走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大?

《算法竞赛入门经典》中是这样解释的:把当前的位置(I,j)看成是一个状态,然后定义状态(I, j)的指标函数d(I, j)为从格子(I,j)出发时能得到的最大和,包括格子(I, j)本身。

解为:d(0,0)

状态转移方程:  d(I,j) = buf[i][j] + max(d(i+1,j),d(i+1, j+1))

可以采用递归的方式实现:

buf[][]  //存储数字三角形中的数据

int dpfunc(int p, int q){

    return buf[p][q] + (p == n ?  0 : ((tmp1 = dpfunc(p+1, q)) > (tmp2 > dpfunc(p+1, q+1)) ? tmp1 :tmp2));

}

采用记忆化搜索的方式:

buf[][]  //存储数字三角形中的数据

d[][]   //存储从i, j 出发到达最后一行所经过节点的最大值

int dpfunc(int p, int q){

ans = &d[p][q];

if(ans )  return ans;

    return ans = buf[p][q]+ (p == n ?  0 :  ((tmp1 = dpfunc(p+1, q)) > (tmp2 >dpfunc(p+1, q+1)) ? tmp1 : tmp2));

}

可以采用递推的方式实现, 结果为buf[0][0]:

void dpfunc(){

for(I = n - 1; I >= 0; --i)

     for(j = 0; j < I; ++j){
       if(buf[i+1][j] >= buf[i+1][j])

  buf[i][j] += buf[i+1][j];

else

  buf[i][j] += buf[i+1][j+1];

}

}

DAG(有向无环图)模型上的动态规划

有向无环图上的动态规划是学习动态规划的基础,很多问题都可以转化为DAG上的动态最长路、最短路或路径计数问题。

通过例题加深题解

例题1:嵌套矩形问题

问题描述如下:有n个矩形,每个矩形可以用两个整数a,b描述,表示它的长和宽,矩形X(a, b)可以嵌套在矩形Y(c, d)中,当且仅当a<c,b<d或者a<d,b<c,选择尽量多的矩形排成一行,使得除了最后一个之外,每一个矩形都可以嵌套在下一个矩形内。

将每个矩形都抽象成一个点,两个点之间邻接,则矩形之间存在嵌套关系,显然,是有向无环图。

状态转移方程为:d(i) = max(d(j)+1)

int dpfunc(int i){

   int ans = &d[i];

if(ans) return ans;

ans = 1;

   for(j = 0; j < n;++j)  if(G[i][j] && ans <dpfunc(j)+1) ans = dpfunc(j)+1;

   return ans;

}

例题2:硬币问题

问题描述如下:有n种硬币,面值分别为v1,v2,。。。vn,每种都有无限多,给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。1=<n<=100,0=<S<=10000。

状态转移方程为:  d(i) = max(d(I – v(j))+1)

解法1:可以选用和上个例题一样的思路,但是可以不用邻接表存储状态之间的关系,将当前面值和x抽象为点,则点集合为0,1,。。。,s,当存在任意i(i<n)时,v[i]<x,则x与x-v[i]则有一条边。因此代码可以如下:

//仅给出求最小值的情况

int dpfunc(int x){

   ans = &d[x];

   if(ans != -1)  return ans;

   ans = 1<<30;                       //用于区别无解的情况

   for(I = 0; I < n;++i) if(v[i] <= x && ans > dpfunc(x-v[i])+1)  ans=dpfunc(x-v[i])+1;

   return ans;

}

d[0] = 0

解法2:可以采用递推的方法,解为max[s],min[s]

void dpfunc(){

  for(I = 1; I <= s;++i){max[i] = -INF; min[i]= INF;}

  min[0] = 0; max[0] = 0;

  for(I = 1; I <= s;++i)

for(j = 0; j < n; ++j){

  if(I >= v[j]){

min[i] = min[i]> min[i-v[j]]+1 ? min[i] : min[i-v[j]]+1;

max[i] = max[i]< max[i-v[j]]+1 ? max[i] : max[i-v[j]]+1;

      }

}

}

例题3:0-1背包问题

有n种物品,每种只有一个,第i个物品的体积为vi,重量为wi,选一些物品装到一个容量为C的背包,使得背包内物品在总体积不超过C的前提下重量尽量大。1=<n<=100, 1=<C<=10000,1=<wi<=1000000.

状态转移方程:d[i][j] = max(d[i-1][j-1], d[i-1][j-v[i]] + w[i]);

解法1:这个似乎不能理解为DAG模型问题,解为d[n][c]代码如下:

v[];

w[];

d[][];  //存储背包总体积不超过j的前提下i个物品所能盛放的最大重量

void dpfunc(){

     memset(&d[0][0],0, maxn * sizeof(int));

     for(I = 1; I <=n; ++i)

       for(j = 0; j <=c; ++j){

       d[i][j] = d[i-1][j];

       if(j >= v[i] && d[i][j] <d[i-1][j-v[i]]+w[i])  d[i-1][j-v[i]]+w[i];

}

}

和例题2比较,可能会对动态规划有更近一步的认识,虽然两个问题都可以用递推的方式解决问题,但是可以看到外层循环和内层循环是不一样的;0-1背包问题在计算的时候讲究计算的层次或者说是计算的次序、顺序。

解法2:运用滚动数组的形式,将二维数组化为一维数组,解为d[c], 代码如下:

v;

w;

d[];

void dpfunc(){

   memset(d, 0, sizeof(d));

   for(I = 1; I <= n;++i){

      scanf(“%d%d”,&v, &w);

      for(j = c; j >=v;--j)

           if(d[j] < d[j-v]+ w) d[j] = d[j-v] + w;

}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值