动态规划,重要的就是状态,现在的状态和过去的状态进行对比更新,选择最优解
算法的应用原理还是递归,找到子问题;
- 记录子问题 就是状态
- 通过递推进行变换 就是状态转移
- 转移的方法取决于题目要求
- 还要分解每一项的含义和关联
- 数组能表示多个值,即下标表示一种值(二维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;
}
看不懂就评论和私信吧