#4.23日

Day0 几个简单DP问题

上学期中开始刷hdu水题100题,在一个dp题目上卡住,遂产生系统性学习算法的念头。后购买《挑战程序设计》,学至DP,发现还是卡壳,于是决定对题目进行一定的分析与总结。

01背包

之前看过大神所写的背包九讲,收益颇多,但是不能很好的理解dp数组,所以在后面的题目中先用递归写一遍程序,在与DP进行比较。

问题复述

在n个重量,价值分别为wivi的物品中挑选总重量不超过W的物品,每件物品仅允许选一次,求选出物品价值总和最大值。

递归描述

起始物品序号为i=0,重量j=W,对于每一件物品都有选或者不选两种选择,而递归终止的条件则为i==n;遍历了所有的物品;当然如果剩余空间 J‘ 无法容纳这件物品时,无法选。
代码如下:

int res(int i;int j){
 int rec;
  if(i==n){
  	rec=0;}
  else if(j<w[i]){
       rec=res(i+1,j);}
  else{
       rec=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
       }//能选,但不一定选,遍历所有可能性。
  return rec;}

DP描述

递归代码稍微好理解一点,但是在处理剩余空间J’可以容纳第i件物品时,每一层都会触发选或者不选两次分支,DP做了一个备忘录,把递归中重复的部分记录下来,以空间换时间。

int dp[1000][1000];
for(int i=0;i<n;i++){
	for(int j=0;j<W;j++){
		if(j<w[i]){
		dp[i+1][j]=dp[i][j];}
		else{
		dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);)}
		    }
		            }
	
cout<<dp[n][W]<<endl;

将物品的重量W划分为W/1个子空间。但是当W很大时,效率就很低了。

完全背包问题

问题复述

与01背包基本相同,但每件物品可以选多余一次;

递归描述

引入一个变量k来表示每件物品被选中的次数,便有:每一次选取总重量k*w[i]小于此时剩余重量J‘,代码如下:

int temp=0;//这个全局变量将避免最终返回选取重量最小物品最多次数;
int res(int i,int j){
 int rec=0;
 if(i==n){
 	rec=0;}
 else if(j<w[i]){
 	rec=res(i+1,j);}
 else{
 	for(int k=0;k*w[i]<=j;k++){
 	temp=max(rec(i+1,j),rec(i+1,j-k*w[i])+k*v[i]);
 	if(rec<temp){
 	rec=temp;}
 	}
 	}
 return temp;}

  

上述代码比起01背包的代码仅仅多了一个循环,在能够选的时候,使用循环来确定具体每一次选取的数量,但是,也正是因为有了循环,返回值在物品可以被选取多次时,总是返回物品选取次数最多的价值,这个价值不一定是最优价值。故定义一个全局变量temp,使得返回值一定为最优解。

dp描述

dp程序基本上也是一样的,多了选取数量的循环部分。代码如下:

for(int i=0;i<n;i++){
	for(int j=0;j<=W;j++){
		for(int k=0;k*w[i]<=j;k++){
			dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]);}}}

就是比01背包多了每次选取的数量。

最长公共子序列问题

问题复述

俩字符串SI,TJ,求出他们最长公共子序列的长度L。

递归描述

如果俩字符串末尾S(i+1)=T(i+1),那么L就是SiTi的最长公共序列+1;或者S1…Si与T1…Tj+1的最长公共子序列;S1…Si+1与T1…Ti的最长公共子序列;

int res(int i,int j){
    int rec=0;
    if(i==0||j==0){
    rec=0;}
    else if(s[i]==t[j]){
    rec=res(i-1,j-1)+1;}
    else{
    rec=max(res(i-1,j),res(i,j-1));}
    return rec;}

    
DP描述

显而易见

for(int i=0;i<n;i++){
	for(int j=0;j<m;j++){
		if(s[i]==t[j]){
			dp[i][j]=dp[i-1][j-1]+1;}
		else{
		dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}
			    }
		    }
	cout<<dp[i][j]<<endl;


最长上升子序列问题

问题描述

有一个数列Sn,求出其子列中最长上升子列Sm长度L;

递归描述
int max0;
int res(int i){
   int rec=1if(i<0){
   rec=0;}
   else{
   for(int j=0;j<i;j++){
   	if(a[j]<a[i]){
   	  rec=max(rec,res(j)+1);}
   	  }
   	  }
  if(rec>max0){
  return max0;}
  res(i-1);
  return  rec;}

   

首先,每一位数的最长上升子序列即他本身;而后判断序号小于他的数是否小于他,这样一个子问题的解便得到了:res(j)+1。

DP描述
int max0=0;
for(int i=0;i<=0;i++){
	dp[i]=1;
   for(int j=0;j<i;j++){
   	if(a[i]>a[j]){
   	dp[i]=max(dp[i],dp[j]+1);	}
   	} max0=max(dp[i],dp[j+1]);
   	}

	

多重部分和问题

问题描述

有n种大小不同数字Ai,每种各Mi个,是否可从这些数字中·取出部分,使他们和为K;

递归描述

这个问题有点类似与前面的多重背包,没有重量限制。总价值为K,每一次减去的价值为k* a[i];自然,有k<=m[i]&&k* a[j]<=j;而后剩余的价值J=j-k*a[i]进入下一个子问题直到剩余价值J=0或者i=n即选完所有可能性。

bool res(int  i,int j){
	bool rec=false;
	if(j==0){
	rec=true;}
	else if(i<n&&j>0){
		for(int k=0;k<=m[i]&&k*a[i]<=j;k++){
			rec=max(rec,res(i+1,j-k*a[i]);}
			}
	else{
	rec=false;}
	return rec;}
	
	
	

DP描述
bool dp[1000][1000];
dp[0][0]=ture;//目标价值为0时比为真;
for(int i=0;i<n;i++){
	for(int j=0;j<=K;j++){
		for(int k=0;k<=m[i]&&k*a[i]<=j;k++){
		dp[i+1][j]=max(dp[i][j],dp[j-k*a[i]]);}
		}
		}
		

总结

可以注意到递归是从顶到底的,从要解决的子问题入手,往回求解,直到边界条件,而DP备忘录是从低到顶的,从初始的子问题来一步步构造出最后子问题的答案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值