用c++解决最少钱问题(基础做法——>动态规划)

在这里十分感谢下面的博客带来的思路

剩余最少的钱_最少剩钱问题-CSDN博客

题目描述

1.超市里有香蕉、苹果、葡萄三种水果,单价为5元/斤、8元/斤、12元/斤,计算出三种水果的平均价格,如果只有n元钱,请问怎么样做,才能使剩下的钱最少?(输入购买的水果种类和斤数,和最终剩下的钱)

2.扩展:现在有m种水果,单价分别为X1,X2,X3,......,Xm,如果只有n元钱,请问怎么样做,才能使剩下的钱最少?(输入购买的水果种类和斤数  种类用第几个表示,和最终剩下的钱)

如果钱能花完,就选择斤数最多的。

深度搜索

参考如图所示模板

 1.

#include<iostream>
#include<math.h> 
using namespace std;
int maxarr[3];//表示钱所能买某种水果的最大数量。 
int prize[3]={5,8,12};//表示水果的价格 
int finarr[3]={0,0,0};//表示最终买的斤数 
int money;
int ans;//表示剩下的最少的钱。 
//leftmoney表示剩下的钱
//index表示当前选择的水果,例如当index为1时,选择的就是5元/斤的苹果。 
void search(int leftmoney,int index,int arr[]){
	//(到达目的地)/达到终止条件 

	if(index==4){
		//保存结果 
			cout<<leftmoney<<endl;
		if(leftmoney<ans){
			ans=leftmoney; 
			finarr[0]=arr[0];
			finarr[1]=arr[1];
			finarr[2]=arr[2];
		}
		return; 
	}
	//表示当前能选择的水果数,从不选到选最多的数量。 
	for(int i=0;i<=maxarr[index];i++){
		//要满足付得起的条件 
		if(leftmoney-i*prize[index]>=0){
			arr[index]=i;
		search(leftmoney-i*prize[index],index+1,arr); 
		} 

	} 
}
int main() {
cin>>money;
maxarr[0]=money/5;
maxarr[1]=money/8;
maxarr[2]=money/12;
ans=money;//ans不能一开始就为0,不然就比较不利大小了。 
int arr[3]={0,0,0};
search(money,0,arr);
cout<<"买的水果有:"<<finarr[0]<<finarr[1]<<finarr[2]<<endl; 


cout<<ans; 
return 0;
}

2.

#include<iostream>
#include<math.h> 
#include<cstring>
using namespace std;
//对于m小于100 
int m;//表示水果种类 
int ans;//表示剩下的最少的钱。
int finarr[100];
int maxarr[100];//表示钱所能买某种水果的最大数量。 
int prizes[100];//表示水果的价格 
void search(int leftmoney,int index,int arr[],int m);
int main() {
cin>>m;
memset(finarr,0,sizeof(finarr)); 
int money;
 ans=money;
for(int i=0;i<m;i++){
	int prize=0;
	cin>>prize;
	prizes[i]=prize;
}
cin>>money; 
for(int i=0;i<m;i++){
	maxarr[i]=money/prizes[i];
}
ans=money;//ans不能一开始就为0,不然就比较不利大小了。 
int arr[m];
memset(arr,0,sizeof(arr));
search(money,0,arr,m);
cout<<"买的水果有:"; 
for(int i=0;i<m;i++){	cout<<"当前的是第"<<i+1<<"个水果"; 	cout<<"数量为"<<finarr[i]<<"  ";
cout<<endl;
}
cout<<endl;
cout<<ans; 
return 0;
}
//m表示一共有什么水果 
//leftmoney表示剩下的钱
//index表示当前选择的水果,例如当index为1时,选择的就是5元/斤的苹果。 
void search(int leftmoney,int index,int arr[],int m){
	//(到达目的地)/达到终止条件 

	if(index==m){
		//保存结果 
		if(leftmoney<ans){
			ans=leftmoney; 
			for(int i=0;i<m;i++){
				finarr[i]=arr[i]; 
			}
		}
		return; 
	}
	//表示当前能选择的水果数,从不选到选最多的数量。 
	for(int i=0;i<=maxarr[index];i++){
		//要满足付得起的条件 
		if(leftmoney-i*prizes[index]>=0){
			arr[index]=i;
		search(leftmoney-i*prizes[index],index+1,arr,m); 
		} 

	} 
}

 

动态规划(完全背包的实现)

当我们把问题转化一下,让剩下的钱最少,就是获取最大价值的货物,比如说香蕉一斤单位为5元一斤,它的价值也是5元一斤,手上的钱减去我们得到的最大价值就是最少的剩下的钱。这就非常像我们的背包问题,重多少,价值多少,背包容量有多少,得到最大价值----相当于完全背包问题。

套用框架如下图:

解释:这个框架怎么来的呢?

 这边有个关键点在于要理解遍历为什么正序能实现重复选取,倒序能实现每个物品只能选取一次。

参考博客:

01背包问题相关优化大全(二维到一维)_01背包问题若是重量相同怎么处理优化-CSDN博客

0,1背包基础逻辑

01背包滚动数组优化(原理:这一行的数据只跟上一行有关,分为奇偶,用%2进行处理---节约空间)

#include <iostream>

using namespace std;
int w[105],v[105];
int dp[2][1005];//滚动数组优化01背包
int main()
{
    int t,m,res;
    scanf("%d%d",&t,&m);
    //读入数据
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w[i],&v[i]);
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=0;j--)
        {
            if(j>=w[i])
            {   //每次%2
                dp[i%2][j]=max(dp[(i-1)%2][j-w[i]]+v[i],dp[(i-1)%2][j]);
            }else{
              dp[i%2][j]=dp[(i-1)%2][j];
            }
        }
    }
    //输出最后一个数据
    printf("%d",dp[m%2][t]);

    return 0;
}

 01背包一维数组优化(另一种滚动数组,覆盖性更强)

参考博客:动态规划:0-1背包问题_0-1背包问题[动态规划]-CSDN博客

博客中说:

对于背包问题其实状态都是可以压缩的。

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层

我的理解是拷贝相当于dp[i-1]直接放在dp[i]的位置上,压缩数组,从两行变成一行。(这时候就需要考虑谁先更新)。

如图所示:(图能解释是否为倒序,非常重要)

dp[i][j]只与左上边的dp[i-1][j]和dp[i-1][j-wight_i]有关,压缩为一维数组的话(i-1就能变成i),我们要的是还没有更新过的值,所以要从右往左遍历。(倒序)

实现:完全背包(正序

完全背包问题-CSDN博客

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历,dp【1】是已经更新过的数据,就能实现反复拿这个物品(上面说的dp[i-1][j-wight_i]先一步更新成了d[i][j-weight_i],,在这个例子中dp【i】因为更新了,已经拿起一次了物品0了)

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

基于本题的代码:

不实现打印所选物品

#include<iostream>
#include<math.h>
#include<cstring>
using namespace std;
int maxarr[3];//表示钱所能买某种水果的最大数量。 
int prize[3][3]={{5,5},{8,8},{12,12}};//表示水果的价格 在动态规划的思路中也代表了物品的价值 
int finarr[3]={0,0,0};//表示最终买的斤数 
int money;
int ans;//表示剩下的最少的钱。

int main() {
cin>>money;
int dp[money+1];//dp[j]----表示在当前钱下能买到的最大物品的价值 
memset(dp,0,sizeof(dp));
for(int i=0;i<=2;i++){
	for(int j=0;j<=money;j++){
		if(j>=prize[i-1][0]){
			dp[j]=max(dp[j-prize[i-1][0]]+prize[i-1][1],dp[j]);
		}
	}
}
for(int i=0;i<=money;i++){
	cout<<"dp i:"<<i<<"  "<<dp[i]<<endl;
}

cout<<money-dp[money]; 
return 0;
}

实现打印所选物品

这2篇博客值得学习,给了我思路,通过观察表格来实现(要打印,就不能用一维数组优化)

【动态规划】01背包问题+查找背包物品_游戏设计中背包中一万个物品怎么快速查找到需要的物品-CSDN博客

背包问题——“完全背包”详解及实现(包含背包具体物品的求解)_完全背包问题 复杂度-CSDN博客

引用别人的话:

设F[i][j]表示出在前i种物品中选取若干件物品放入容量为j的背包所得的最大价值。那么对于第i种物品的出现,我们对第i种物品放不放入背包进行决策。如果不放那么F[i][j]=F[i-1][j];如果确定放,背包中应该出现至少一件第i种物品,所以F[i][j]种至少应该出现一件第i种物品,即F[i][j]=F[i][j-C[i]]+W[i]。为什么会是F[i][j-C[i]]+W[i]?因为F[i][j-C[i]]里面可能有第i种物品,也可能没有第i种物品。我们要确保F[i][j]至少有一件第i件物品,所以要预留C[i]的空间来存放一件第i种物品。

具体背包中放入那些物品的求法和01背包情况差不多,从F[N][V]逆着走向F[0][0],设i=N,j=V,如果F[i][j]==F[i][j-C[i]]+W[i]说明包里面有第i件物品,同时j -= C[i]。完全背包问题在处理i自减和01背包不同,01背包是不管F[i][j]与F[i-1][j-C[i]]+W[i]相不相等i都要减1,因为01背包的第i件物品要么放要么不放,不管放还是不放其已经遍历过了,需要继续往下遍历而完全背包只有当F[i][j]与F[i-1][j]相等时i才自减1。因为F[i][j]=F[i-1][j]说明背包里面不会含有i,也就是说对于前i种物品容量为j的背包全部都放入前i-1种物品才能实现价值最大化,或者直白的理解为前i种物品中第i种物品物不美价不廉,直接被筛选掉。

 

最终代码:

#include<iostream>
#include<math.h>
#include<cstring>
using namespace std;
int prize[4][4]={{0,0},{5,5},{8,8},{12,12}};//表示水果的价格 在动态规划的思路中也代表了物品的价值 
int finarr[4]={0.0,0,0};//表示最终买的斤数 
int money;

int main() {
cin>>money;
int dp[4][money+1];//dp[i][j]----表示在当前物品0-i中当前钱j下能买到的最大物品的价值 
memset(dp,0,sizeof(dp));
for(int i=1;i<=3;i++){
	for(int j=0;j<=money;j++){
		dp[i][j]=dp[i-1][j];
		if(j>=prize[i][0]){
			dp[i][j]=max(dp[i-1][j],dp[i][j-prize[i][0]]+prize[i][1]);
		}
	}
}
int i=3;
int j=money;
while(i>0&&j>0){
	if(dp[i][j]==dp[i][j-prize[i][0]]+prize[i][1]){
		finarr[i]++;
		j=j-prize[i][0];
		cout<<"目前j\i为"<<j<<" "<<i<<endl; 
	}else{
		i=i-1;
	}
}
for(int i=1;i<=3;i++){
	cout<<i<<"<-编号      数量->"<<finarr[i]<<endl;
}
//for(int i=0;i<=3;i++){
//	for(int j=0;j<=money;j++){
//	cout<<dp[i][j]<<" ";
//}
cout<<endl;
}
cout<<money-dp[3][money]; 
return 0;
}

扩展水果品种到m

#include<iostream>
#include<math.h>
#include<cstring>
using namespace std;
int prize[101][2];//表示水果的价格 在动态规划的思路中也代表了物品的价值 
int finarr[101];//表示最终买的斤数 
int money;
int m;//表示水果品种数 
int main() {
cin>>m;
for(int i=1;i<=m;i++){
	int a;
	cin>>a;
	prize[i][0]=a;
	prize[i][1]=a;
}
cin>>money;
int dp[m+1][money+1];//dp[i][j]----表示在当前物品0-i中当前钱j下能买到的最大物品的价值 
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++){
	for(int j=0;j<=money;j++){
		dp[i][j]=dp[i-1][j];
		if(j>=prize[i][0]){
			dp[i][j]=max(dp[i-1][j],dp[i][j-prize[i][0]]+prize[i][1]);
		}
	}
}
int i=m;
int j=money;
while(i>0&&j>0){
	if(dp[i][j]==dp[i][j-prize[i][0]]+prize[i][1]){
		finarr[i]++;
		j=j-prize[i][0];
		cout<<"目前j\i为"<<j<<" "<<i<<endl; 
	}else{
		i=i-1;
	}
}
for(int i=1;i<=3;i++){
	cout<<i<<"<-编号      数量->"<<finarr[i]<<endl;
}
//for(int i=0;i<=3;i++){
//	for(int j=0;j<=money;j++){
//	cout<<dp[i][j]<<" ";
//}
cout<<endl;

cout<<money-dp[m][money]; 
return 0;
}
  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值