一、背包问题总结**
背包问题,首先要搞清楚它的本质是什么?它的原理是什么?
背包问题指这样一类问题,题意往往可以抽象成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。——百度百科
先用最简单的一个问题来描述背包问题:
有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不能白选啊,怎么也得再多学点东西才行啊,这周的状态比上周好了不少,但是还没有回到假期前的状态,希望可以再进步一点吧