动态规划入门(一)(0-1背包等背包问题)

动态规划基本思想

理解动态规划基本思想

  1. 确定状态变量(函数)
  2. 确定状态转移方程(递推关系)
  3. 确定边界

各种背包问题内容、思想、例题和代码实现

基础 0-1背包问题

0-1背包问题的基础思想
0-1背包详细题解
0-1背包简化/降维思想 – 有点冗杂,不过讲的很详细,主要就是:1. 逆序,因为本层得到的信息是由上一层递推得到的,所以逆序保护上一层信息,以保证在本层得到的信息的正确性;2. 降维的根本在于信息在递推过程中可被覆盖,不会影响结果,因为i层信息只与i-1层有关,与i-2层无关。
题目:最基础的0-1背包问题

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int wei[N],val[N];
//int f[N][N];//f[i][j]表示i件物品放入容量为j的背包的最大价值 
int f[N]; 
int n,wemax;

int main()
{
	cin>>n>>wemax;//4 5
	for(int i=1;i<=n;i++)
	{
		cin>>wei[i]>>val[i];
	}
	/*未简化: 
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=wemax;j++)//j是容量 对容量的[0,wemax]每个情况都考虑放与不放,保留较大值 
		{
			f[i][j]=f[i-1][j];//不放第i个物品的值 
			if(j>=wei[i]) f[i][j]=max(f[i][j],f[i-1][j-wei[i]]+val[i]);
			/* 解释: 
			j>=wei[i] 背包本身容量足够放入第i个物品 
			比较不放第i个物品价值较大还是放比较大(放的话可能之前的一些物品要取出) 
			f[i][j],即 f[i-1][j]为不放时的价值 
			f[i-1][j-wei[i]]+val[i]为 容量为j-wei[i]时的最大价值+当前物品的价值 
			即此时向之前的情况取价值最大且能放下第i个物品的情况
			抽象的问题在于为什么是f[i-1]就行 
			因为根据定义 f[i-1]也就是其情况下的最优解了,最优解+v必然是f[i]下的最优 
			
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=wemax;j++)
		{
			cout<<f[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<f[n][wemax]<<endl;
	*/
	//简化:
	for(int i=1;i<=n;i++)
	{
		//逆序 
		//第因为i层的f[j]的确定一定要用到第i-1层的f[j]不能用本层已经得到的f[j]所以一定要用逆序。
		//一维的存储在于覆盖之前的信息即可,减少空间 
		for(int j=wemax;j>=wei[i];j--)
		{
			f[j]=max(f[j],f[j-wei[i]]+val[i]);
			//cout<<f[j]<<" ";
		}
		//cout<<endl;
	}
	/*for(int i=1;i<=n;i++)
	{
		cout<<f[i]<<" ";
	}*/
	cout<<f[wemax]<<endl;
	return 0;
}
/*
未简化: 
f[i][j]:
j:1 2 3 4 5
0 2 2 2 2 2
0 2 4 6 6 6
0 2 4 6 6 8
0 2 4 6 6 8
简化:
f[i]: 2 4 6 6 8
循环内变化:
5 4 3 2 1 <-顺序 
2 2 2 2 2
6 6 6 4
8 6 6
8 6
*/

有两个限制条件的0-1背包问题 – NASA的食物计划

题目:NASA的食物计划

#include<bits/stdc++.h>
using namespace std;
const int N=1e3;
int wei[N],v[N],ca[N];
int f[N][N];
struct node{
	int v;
	int w;
	int ca;
}food[N];
int main()
{
	int maxv,maxw;
	cin>>maxv>>maxw;
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>food[i].v>>food[i].w>>food[i].ca;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=maxv;j>=food[i].v;j--)//第一重限制
		{
			for(int k=maxw;k>=food[i].w;k--)//第二重限制
			{
				f[j][k]=max(f[j][k],f[j-food[i].v][k-food[i].w]+food[i].ca);
			}
		}
	}
	cout<<f[maxv][maxw];
	return 0;
}

物品可无限次被选择的完全背包问题

题目:完全背包问题,即再0-1背包基础上多一层循环,详细文字题解

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N];
int wei[N],val[N];
/*
将选k个物品与wei[i]结合 f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])
优化:
f[i , j ] = max( f[i-1,j] , f[i-1,j-wei]+val ,  f[i-1,j-2*wei]+2*val , f[i-1,j-3*wei]+3*val , .....)
f[i , j-v]= max(            f[i-1,j-wei]     ,  f[i-1,j-2*wei] + val , f[i-1,j-3*wei]+2*val , .....) 
-------->则 f[i][j]=max(f[i-1][j] , f[i,j-wei]+val)  ;
对比0-1背包 f[i][j]=max(f[i-1][j] , f[i-1,j-wei]+val);
在进一步优化前  f[i][j]=f[i-1][j];
进一步优化后:0-1背包为 f[i-1,j-wei]+val,则需要逆序,因为和i-1层有关,范围为[m, wei[i]] 
但是完全背包为 f[i,j-wei]+val),不用逆序,从小到大即可,范围为[wei[i], m] 
-------->状态转移方程:f[j] = max(f[j],f[j-wei[i]]+val[i]) 
*/
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>wei[i]>>val[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=wei[i];j<=m;j++)//从小到大枚举 因为i层和i-1层无关 
		{
			f[j]=max(f[j],f[j-wei[i]]+val[i]);
		}
	}
	cout<<f[m];
}

物体可有限次被选择的多重背包问题1

题目:多重背包问题1
在完全背包问题基础上加一个k<=num[i]&&k*wei[i]<=j的限制,限制物品被选择次数。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N][N];
int wei[N],val[N],num[N];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>wei[i]>>val[i]>>num[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			for(int k=0;k<=num[i]&&k*wei[i]<=j;k++)
				f[i][j]=max(f[i][j],f[i-1][j-wei[i]*k]+val[i]*k);
		}
	}
	cout<<f[n][m];
	return 0;
}

多重背包问题2,二进制优化方法

题目:多重背包问题2题目视频讲解
没有特别理解,有空再仔细听听视频讲解

#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int f[N];
struct Good{
	int wei,val;
};
int main()
{
	int n,m;
	cin>>n>>m;
	vector<Good> goods;	
	for(int i=1;i<=n;i++)
	{
		int w,v,s;
		cin>>w>>v>>s;
		for(int k=1;k<=s;k*=2)
		{
			s-=k;
			goods.push_back({w*k,v*k});
		}
		if(s>0) goods.push_back({w*s,v*s});
	}
	for(auto good:goods)
	{
		for(int j=m;j>=good.wei;j--)
		{
			f[j]=max(f[j],f[j-good.wei]+good.val);
		}
	}
	cout<<f[m];
	return 0;
}

分组背包问题

题目:分组背包问题
题解:详细文字题解视频题解
据说是多重背包问题的一般情况

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N];
int wei[N][N],val[N][N],s[N];//s代表第i组物品个数
/*
0-1:前i个物品,体积小于j的最大价值
分组背包:前i组物品,体积小于j的最大价值 
*/ 
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		for(int j=0;j<s[i];j++)
		{
			cin>>wei[i][j]>>val[i][j];
		}
	}
	for(int i=1;i<=n;i++)//阶段 
	{
		for(int j=m;j>=0;j--)//i、j共同构成状态 
		{
			for(int k=0;k<s[i];k++)//k为决策 
			{
				if(wei[i][k]<=j)
				{
					f[j]=max(f[j],f[j-wei[i][k]]+val[i][k]);
				}
			}
		}
	}
	cout<<f[m]<<endl;
	return 0;
}

0-1背包扩展题 – 摆花

题目:摆花
解析:洛谷详解
思路:
在这里插入图片描述
这道题没有特别懂,后续再来考虑补充
未优化代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=105, mod = 1000007;
int n, m, a[maxn], f[maxn][maxn];
int main()
{
    cin>>n>>m;
    for(int i=1; i<=n; i++) cin>>a[i];
    f[0][0] = 1;
    for(int i=1; i<=n; i++)
       for(int j=0; j<=m; j++)
           for(int k=0; k<=min(j, a[i]); k++)
              f[i][j] = (f[i][j] + f[i-1][j-k])%mod;
    cout<<f[n][m]<<endl;
    return 0;
}

优化版代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N];
int a[N];
int mod=1000007;
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	f[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=0;j--)
		{
			for(int k=1;k<=min(a[i],j);k++)
			{
				f[j]=(f[j]+f[j-k])%mod;
			}
		}
	}
	cout<<f[m]<<endl;
}

更多背包问题汇总讲解文章补充:
文章1,除本文所含内容外,还有混合背包问题,即每个物品可被选择次数不同,和“摆花”有点像,即限定每种物品被选择次数,但每种物品选择次数都不一样

文章2,详细分析了各个模型和推导状态转移式子

总结

在动态规划入门情况下,写了好几道和背包有关的题目,但是还是一知半解,没有独立分析和解决新动态规划问题的能力,只能根据记忆去回忆已经做过的题目和使用方法,再考虑如何解决新题目。希望后面题目越做越多能让情况不要这么局促吧…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值