5.16总结

本文详细介绍了背包问题的动态规划解决方案,包括完全背包问题的动态规划状态转移方程及AC代码。同时,提供了使用动态规划解决不同背包问题的实例,如作业B中的最小余额问题和作业E的商品购买问题。作者分享了对背包问题的理解和感悟,并表示将继续深入学习算法。
摘要由CSDN通过智能技术生成

一、背包问题总结**
背包问题,首先要搞清楚它的本质是什么?它的原理是什么?
背包问题指这样一类问题,题意往往可以抽象成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。——百度百科
先用最简单的一个问题来描述背包问题:
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
每个物品都有它自己的属性:体积和价值
解题思路:用动态规划的思路,阶段就是“物品的件数”,状态就是“背包剩下的容量”,那么f [i,v]就设为从前 i 件物品中选择放入容量为 v 的背包最大的价值。那么状态转移方程为:

f[i][v]=max{ f[i-1][v],f[i-1][v-w[i]]+val[i] }

只考虑子问题“将前 i 个物品放入容量为 v 的背包中的最大价值”那么考虑如果不放入i,最大价值就和 i 无关,就是f[i-1][v] , 如果放入第 i 个物品,价值就是 f[i-1][v-w[i]]+val[i]我们只需取最大值即可。
其实背包问题跟动态规划解题相似:
都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。动态规划具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。(跟递归也有些相似)
而背包问题又分为几种:
完全背包、多重背包、分组的背包问题…
以下为做题总结
1.
有n件物品和容量为m的背包,给出i件物品的重量以及价值value,还有数量number,求解让装入背包的物品重量不超过背包容量W,且价值V最大 。
特点:题干看似与01一样,但它的特点是每个物品可以无限选用。
一般用dp数组来计算动态规划问题,从以下两个方面对动态规划问题进行表示
集合:
v集合:物品价值
w集合:物品重量
从前i个物品里面选取总重量<=j的所有物品的选法,与01背包的区别在于,每一种物品是可以无限选择的
属性:
max
min
count
本题属性是属于求最大价值,为max
对于完全背包的问题,遵从01背包的策略,是选择放或者不放两个状态,但是每一种物品可以放无限个,因此可以转换为:实际上我们对于一个物品的选择就是放多少个的问题:
我们假设一种物品选择k个(因为背包本身是有重量限制的,所以是k个而不是无限个)

选择放进去
如果选择放进去,还需要考虑放进去多少个,即:
1, 2, 3, ···, k-1, k个
表示在上一个物品的状态的时候,我的当前背包重量j需要减去当前k个物品的重量kw[i],并且整个背包的价值需要加上当前k个物品的价值kv[i],则状态方程为:

dp[i][j] = dp[i-1][j-k*w[i]] + k*v[i]

选择不放进去
实际上如果选择不放进去的时候,表示放进去的是0个,需要减去的kw[i]和需要加上的kv[i]都为0选择不放进去的状态方程则为:

dp[i][j] = dp[i-1][j]

由此我们可以得到状态转移方程

dp[i][j] = max(dp[i-1][j-k*w[i]] + k*v[i], dp[i-1][j])

AC代码:

#include<iostream>
#include<cstring>
using namespace std;
int dp[21][1010];
int w[21],c[21];
int main()
{
	int N,V;
	cin>>N>>V;
	for(int i=1;i<=N;++i)
		cin>>w[i]>>c[i];
	for(int i=1;i<=N;++i)
	{
		for(int j=0;j<=V;++j)
		{
			if(j>=c[i])
				dp[i][j]=max(dp[i][j-c[i]]+w[i],dp[i-1][j]);
			else 
				dp[i][j]=dp[i-1][j];
		}
	}
	cout<<dp[N][V]<<endl;
	return 0;
	}

作业B:
https://vjudge.net/contest/436354#overview
题意:
有m块钱,选购n中饭菜,每种菜只可购买一次,当余额不小于5时,可买任何饭菜,求最终饭饭卡余额最小。
余额小于5时,不能购买。
思路:
用sort排序将饭菜价格从小到大排序,将最贵的饭菜挑出,用c-5尽可能买多的饭菜,最后用剩余接近5的余额,买最贵的菜,将买的饭钱加一块,用m减去花的钱就是结果。
AC代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[1010],v[1010];//价钱
int main()
{
    int t;
    while(cin>>t)
    {
        if(t==0)
            break;
        memset(a,0,sizeof(a));
        int i,j,c;
        for(i=1;i<=t;i++)
            cin>>v[i];
        sort(v+1,v+1+t);
        cin>>c;
        if(c>=5)
        {
            for(i=1;i<t;i++)
            {
                for(j=c-5;j>=v[i];j--)
                    a[j]=max(a[j],a[j-v[i]]+v[i]);
            }
            cout<<c-a[c-5]-v[t]<<endl;
        }
        else
            cout<<c<<endl;
    }
    return 0;
}

作业E:
题目大意:有n件商品和购买余额m,每件商品包含如下信息:商品的价格p,允许购买该商品的最少余额q,以及商品的价值v。求用余额m最多能购买到的商品价值。
解题思路:这题在01背包的基础上多了一个限制商品购买的条件。普通的01背包考虑的是每一件商品买或者不买,和购买顺序无关,但这题加了条件限制后,先买后买对是否能买一件商品就有很关键的影响了。
举个例子:A商品:p1 = 1,q1 = 3 ,v1 = 1;
B商品:p2 = 5,q2 = 5 , v2 = 1;
如果这时候余额为6,先买A商品后,余额为5,可以继续购买B商品,两件商品都能购买。如果先买B商品,余额为1,小于q1,不能购买A商品。即若要购买两件商品,先买A商品时需满足m >= p1 + q2 = 6, 先买B商品时需满足m >= p2 + q1 = 8,明显先买A商品需要的余额最少,即p1 + q2 < p2 + q1 时 应先买p1对应的商品,不等式转换一下为:p1 - q1 < p2 - q2,即 p 和 q 差值小的商品应先购买。但要注意!01背包的操作顺序是从后往前购买的,即最后一个商品都是确定购买的,因此sort排序时把p 和 q 差值小的商品放在后面。
在输入商品信息时纪录p-q值,然后对其sort从大到小排序,按此顺序进行01背包计算即可。
AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 5005
int dp[maxn];
 
struct product{
    int q,p,v;
    int d;
}pro[505];
 
bool cmp(product x,product y){
    return x.d > y.d; 
}
 
int main(void){
    int n,m;
    while(cin>>n>>m){
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++){
            cin>>pro[i].p>>pro[i].q>>pro[i].v;
            pro[i].d=pro[i].p-pro[i].q; 
        }
        sort(pro,pro+n,cmp);
        int cur=m;
        for(int i=0;i<n;i++){
            for(int j=m;j>=pro[i].p;j--){
                if(j>=pro[i].q)      
                    dp[j]=max(dp[j],dp[j-pro[i].p]+pro[i].v);
                else
                    break;
            }
        }
        cout<<dp[m]<<endl;
    }
}

一道Codeforces的题:
Educational Codeforces Round 109 [Rated for Div. 2]
A. Potion-making
题目很简单,只要把K和ans一直除他们的最大公因数,直到比例为K为止即可,此时输出ans就是结果
此处用到了递归函数:

int f(int a,int b)
{
    return b==0?a:f(b,a%b);
}

AC代码:

#include <iostream>
 
using namespace std;
int f(int a,int b)
{
    return b==0?a:f(b,a%b);
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int k,ans=100;
        cin>>k;
        while(f(ans,k)!=1)
        {
            ans/=f(ans,k);
            k/=f(ans,k);
        }
        cout<<ans<<endl;
    }
    return 0;
}

**感悟:**一周过去,讲了新的算法,二分三分法,背包问题还没整很明白,就开始新的算法,由于昨天的期中考试,今天早上才发现背包的作业没做完,于是赶了一天的作业,感觉有点收获,却还不是那么明白背包问题,而且课程马上就要结束了,感觉到了一点危机感,动力增加的同时压力也大了,现在的想法就是ACM不能白选啊,怎么也得再多学点东西才行啊,这周的状态比上周好了不少,但是还没有回到假期前的状态,希望可以再进步一点吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值