动态规划————背包问题

用了两天的时间把讲解背包问题的视频看了一遍,加深了自己对dp的理解和认识。dp问题可以从两个方面入手,状态表示 状态计算 而背包问题的状态表示一般都是两维 dp[i][j]表示从前i个物品中选且总体积不超过j的最大值,而背包问题的状态计算也是大同小异。

01背包

01背包是最简单的背包问题,就是每件物品最多只能取一次,这样就可以得出01背包的状态转移方程 dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]),这便是01背包最基础的解法

int slove1(){ //朴素做法 时间复杂度o(n)^2;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
		    dp[i][j]=dp[i-1][j];
			if(j>=v[i])
			dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
		}
	}
	return dp[n][m];
}

但是又可以看出,dp数组的第一维永远都是i-1,所以我们可以把第一维度去掉然后就可以得出一维的01背包解法

int slove2(){//优化一维做法 时间复杂度o(n)2;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j--){
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	return f[m];
}

在写一维的01背包解法的时候要注意,原转移方程中的dp[i][j]是与第i-1层进行比较的,所以我们应该从大到小枚举j,确保当前的j是没有被更新过的j;

完全背包

完全背包问题是指所有物品的数量是无限的,容易得出完全背包的状态计算为dp[i][j]=max(dp[i-1][j-kv[i]]+kw[i]) k=(0,1,2,3…),不难写出完全背包的朴素写法

int slove1(){ //朴素做法 o(n)^3
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int x=0;x*v[i]<=j;x++){
				if(x*v[i]<=j)
				dp[i][j]=max(dp[i][j],dp[i-1][j-x*v[i]]+x*w[i]);
			}
		}
	}
	return dp[n][m];	
}

dp[i][j]=max(dp[i-1][j],dp[i-1][k-v[i]]+w[i],dp[i-1][k-2v[i]]+2w[i]…)
dp[i][j-v[i]]=max( dp[i-1][k-v[i]], dp[i-1][k-2v[i]]+w[i])
通过上面两个式子可以发现dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])
从而得到优化后的完全背包

int slove2(){ //优化做法 o(n)^2;
	 for(int i=1;i<=n;i++){
	 	for(int j=0;j<=m;j++){  
	 	    dp[i][j]=dp[i-1][j];
	 	    if(j>=v[i])
	 		dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
		 }
	 }
	return dp[n][m];
}

根据将01背包聪二维优化为一维的经验,我们可以发现完全背包也可以将二维优化为一维,只不过完全背包的第一维为第i层,所以要从小到大枚举j

int slove3(){//将二位优化为一维数组,因为f[j-v[i]]是第i层的,所以要从小到大枚举j
    for(int i=1;i<=n;i++){
        for(int j=v[i];j<=m;j++){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    return f[m];
}

多重背包

多重背包即每个物品的数量是有限的,第i个物品的数量是si,其实多重背包的转移方程跟完全背包的基本一致,就是多个判断数量的条件
dp[i][j]=max(dp[i-1][j-kv[i]]) k=(0,1,2,3…)

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<vector>
#pragma comment(linker, "/STACK:102400000,102400000")
typedef long long ll;
using namespace std;
const int N=1e2+10;
int  v[N],w[N],s[N],dp[N][N],f[N];
int n,m; 
int slove(){//朴素做法 时间复杂度o(n)^3; 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			dp[i][j]=dp[i-1][j];
			for(int x=0;x*v[i]<=j&&x<=s[i];x++){
				dp[i][j]=max(dp[i][j],dp[i-1][j-x*v[i]]+x*w[i]);
			}
		}
	}
	return dp[n][m];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>v[i]>>w[i]>>s[i];
	}
	int ans=slove1();
	cout<<ans;
}

有了二维转一维的经验,我们不难发现多重背包转一维和完全背包转一维基本类似,但是不要照搬,因为我们还需要考虑的一个条件就是第i件物品的个数为si个。
假设第1件物品有1023个,那么我们可以分组
1 2 4 8 16 32 64 128 256 512
我们可以发现第一组和第二组按照需要的方式结合可以得到1——3中的任意一个数,之后在于第三组结合能得到1——7中的任意一个数,后面的以此类推,可以发现这10组的自由结合可以得到1——1023中的任意一个数,所以我们可以把这些组都存起来,v[1]=1v[i],v[2]=2v[i]…;将这些数都存起来,我们就得到了一个类似01背包的问题,那么问题就迎刃而解了

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 12000;
int v[N],w[N],f[N],s[N];
int n,m;
// 多重背包一维做法 
int main(){
    cin>>n>>m;
    int cnt=1;
    int q,y,s;
    for(int i=1;i<=n;i++){
        cin>>q>>y>>s;
        int x=1;
        while(x<s){
            v[cnt]=x*q;
            w[cnt]=x*y;
            s-=x;
            x=x*2;
            cnt++;
        }
        if(s!=0){
            int res=s;
            v[cnt]=s*q;
            w[cnt]=s*y;
            cnt++;
        }
    }
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+f[i]);
        }
    }
    cout<<f[m];
}

由于我们要将所有组都存入 v和w数组,所以我们要将这个数组开大一点。

分组背包

相信能看到这里,一定对背包有了较深入的认识,所以下面我就不会再那么细致的讲解了
分组背包:每一组有若干个物品,但每组最多只能取一个。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e2+10;
int s[N],v[N][N],w[N][N],dp[N][N];
int f[N];
int n,m;
//分组背包,每一组只能取一个 
int slove1(){//朴素写法 时间复杂度o(n)^3;
       for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dp[i][j]=dp[i-1][j];
            for(int x=1;x<=s[i];x++){
                if(j>=v[i][x])
                dp[i][j]=max(dp[i][j],dp[i-1][j-v[i][x]]+w[i][x]);
            }
        }
    }
    return dp[n][m];
}
int slove2(){//优化为一维写法
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--){
            for(int x=1;x<=s[i];x++){
                if(j>=v[i][x])
            f[j]=max(f[j],f[j-v[i][x]]+w[i][x]); 
            }
           
        }
    }
    return f[m];
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=1;j<=s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    int ans=slove2();
    cout<<ans;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值