01背包 动态规划 黑书 初学通俗版

动态规划,重要的就是状态,现在的状态和过去的状态进行对比更新,选择最优解

算法的应用原理还是递归,找到子问题;

  • 记录子问题 就是状态
  • 通过递推进行变换 就是状态转移
  • 转移的方法取决于题目要求
  • 还要分解每一项的含义和关联
  • 数组能表示多个值,即下标表示一种值(二维duozhong),数组存储的数代表一种值

硬币问题引入

最少硬币问题

有n种硬币,面值分别为v1,v2,…vn,数量无限。输入非负整数s,选用硬币,使其和为s。要求输出最少的硬币组合。

  • 共有三个变量,面值,总钱数,和硬币数
  • 一个面值一个面值赋值,带有不同的硬币数就是状态
  • 能用最少的硬币数就替换,就是状态转移

本题关键在与如何记录和如何转移

  • 用数组coin记录硬币数,coin的下标[ i ] 代表钱数 状态
  • 面值用type
  • 用min去比较谁最小,比较的对象是同样钱数的现在的硬币数和换成另一种硬币后type的硬币数取最小 状态转移
  • 换硬币就是 钱数减去现在对应的面值type ,硬币数加1,因为对应的面值可以直接替换成1个硬币
#include<bits/stdc++.h>
using namespace std;
int type[100],coin[100];
const int M=100000;
int m=100;
int main()
{
	int n,s;
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>type[i];
	for(int j=0;j<m;j++)//初始化每一项记录值 
	coin[j]=M;
	coin[0]=0;
	//打表 
	for(int i=0;i<n;i++)//硬币种类 
	for(int j=type[i];j<m;j++)//每一种硬币种类对应的钱 
	coin[j]=min(coin[j],coin[j-type[i]]+1);//更新状态
	// 更新每一种情况与换一种情况谁更划算 
	while(cin>>s)
	cout<<coin[s]<<endl;
	return 0;
}

打印最少硬币组合

打印出刚才的硬币组合

  • 找一个数组记录,数组的下标表示钱数,数组的存储内容为这个钱数下最后一个面值
  • 输出的时候倒推,输出完钱数对应的最后一个面值,循环
  • 因为每一个钱数对应的都是最优状态下的最后一个面值
#include<bits/stdc++.h>
using namespace std;
int type[100],coin[100];
const int M=100000;
int m=100;
int vis[100]; 
int main()
{
	int n,s;
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>type[i];
	for(int j=0;j<m;j++)//初始化每一项记录值 
	coin[j]=M;
	coin[0]=0;
	//打表 
	for(int i=0;i<n;i++)//硬币种类 
	for(int j=type[i];j<m;j++)//每一种硬币种类对应的钱 
	{
		if(coin[j]>coin[j-type[i]]+1)
            // 比较每一种情况与换一种情况谁更划算 
		{
		vis[j]=type[i];
            //做记录,每一种最优新状态 更新 换的状态的值 
		coin[j]=coin[j-type[i]]+1;//更新
	    }
    }	
	while(cin>>s){	
cout<<coin[s]<<endl;
cout<<"面额"<<endl;
for(int j=s;j>0;j-=vis[j])
    //每次减去最优状态下的面值,就更新到上一个最优状态 
cout<< vis[j]<<endl;
}
	return 0;
}

所有硬币组合 coin change

不是最优解了,是所有解的情况

还有限制要求,所有解中的情况里硬币数要小于100

  • 需要一个新的数组来记录,为了能同时满足硬币数和钱数两者,用二维数组
  • dp[ money][ coin ],内容为还是硬币数,所以这次是上一个状态加上新状态,不是替换,节省很多空间。叫做转移矩阵
  • 在一个面值下
  • 更新状态的依据是在现有的钱财和硬币数量**(原有方案数),加上,硬币的钱被面值替代(钱财还是不变),硬币数减1的情况(也是上一个状态的和)因为是在新的面值下,所以多了这种情况(新方案数)**

```cpp
```c++
#include<bits/stdc++.h>
using namespace std;
int type[5]={1,5,10,25,50};
int coin[100];
const int M=100000;
int m=100;
int dp[251][100]; 
void sorve()
{
	dp[0][0]=1;   //把0位归1 
	for(int i=0;i<5;i++)//硬币种类 
	for(int c=1;c<100;c++)//硬币数量 
	for(int j=type[i];j<250;j++)//钱的状态 
	//转移矩阵 
	dp[j][c]=dp[j][c]+dp[j-type[i]][c-1];//更新每一个状态
	//取决于现在的状态,加上新种类的状态 
	return ;
}
int main()
{
	int s;
	int d[251]={0};
	sorve();
	for(int j=1;j<250;j++)
	for(int c=1;c<100;c++)
	d[j]+=dp[j][c];//统计每一种钱有几种情况 
	while(cin>>s)
	cout<<d[s]<<endl;
	return 0;
}

01背包

给定n种物品和一个背包,物品的重量是wi,价值为vi,背包的总容量为c。在装入物品的背包时对每种物品i只有两种选择,装入和不装入

(称为01背包)。如何选择使装入价值最大?

  • 子问题就是从每个容量下取最优解
  • 子问题的包含是在上一个最优解的情况下推新的最优解

有了硬币的思路,就可以想到,还是从不同容量下,取最优解

  • 从重量即容量入手,同一容量下,不同的选择
  • 还是从第一个物品开始选
  • 状态就是最优解,能装进这个背包 ,记录状态用 dp[C][N],容量和物品的种类,内容代表价值
  • 转移状态就是,装进背包后所带来的价值会不会比不装高(有可能因为容量不够所以会有不装和装了新的丢了旧的的情况)用max
  • 每个状态都要赋值
```c++
#include<bits/stdc++.h>
using namespace std;
const int C=100;
const int N=100;
int w[N],v[N];
int dp[C][N]={0};//记录状态 
int n,c;
int ans()
{
		for(int i=1;i<=n;i++)//每件物品 
	for(int j=1;j<=c;j++)//每个容量 
	//在同一容量下,选择哪个物品
	if(j>=w[i])
	//依据上一个状态 
	//对没选这个物品和选了这个物品作比较,大值者胜 
	dp[j][i]=max(dp[j][i-1],dp[j-w[i]][i-1]+v[i]) ;
	//更新状态 
	//以后再比较就是每个不同容量下最优解的比较 
	else
	dp[j][i]=dp[j][i-1];
	//!!!记得考虑装不下也要赋值给装不下的情况 
	return dp[c][n];
 } 
int main()
{
	
	cin>>n>>c;
	for(int i=1;i<=n;i++)//重量 
	 cin>>w[i];
	for(int i=1;i<=n;i++)//价值 
	 cin>>v[i];

	cout<<ans();
	return 0;
	
 } 

滚动数组

因为有没有硬币数量都会被同一钱财下不同的解法所替代,所以干脆不要dp[C] [N] 里的N了

但是各个判断条件就要适当的改变

  • 状态转移的要求容量大于这个物品的重量才能进入,原来用if判断,现在反过来,从容量c开始减到装不下这个物品开始,就可以不用if了
  • 状态转移依据是根据上一个状态和新状态对比看需不需要转移状态
#include<bits/stdc++.h>
using namespace std;
const int C=100;
const int N=100;
int w[N],v[N];
int dp[C]={0};//记录状态 
int n,c;
int ans()
{
	for(int i=1;i<=n;i++)//每件物品 
	for(int j=c;j>=w[i];j--)//反过来循环,减少判断//还是要大于w[i] 
	dp[j]=max(dp[j],dp[j-w[i]]+v[i]) ;//更新成最新的状态
	return dp[c];
 } 
int main()
{
	
	cin>>n>>c;
	for(int i=1;i<=n;i++)//重量 
	 cin>>w[i];
	for(int i=1;i<=n;i++)//价值 
	 cin>>v[i];
	cout<<ans();
	return 0;
	
 } 

看不懂就评论和私信吧

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值