01背包
给定一个容量为c的背包,有n个物品,第i个质量为wi,价值为vi,求背包的最大价值
由于每种物品只有1个,因此每个物品只有01两种状态,即拿和不拿
用V【i,j】表示在面对第i个物品且背包容量为j时,背包内的最大价值
那么显然,V【0,j】和V【i,0】都应该初始化为
解决问题时有两种情况,
(1)当前背包容量不足以放入i(j<wi),那么V【i,j】=V【i-1,j】
(2)当前背包容量足以放入i(j>=wi),此时要选择i放还是不放,如果不放V【i,j】=V【i-1,j】;如果放V【i,j】=V【i-1,j-wi】+vi。取两者之间的那个较大的值
状态转移方程
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
2 | 0 | 0 | 0 | 0 | 8 | 8 | 10 | 10 | 10 | 10 | 18 | 18 | 18 |
3 | 0 | 0 | 6 | 6 | 8 | 8 | 14 | 14 | 16 | 16 | 18 | 18 | 24 |
4 | 0 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 |
5 | 0 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 21 | 24 |
6 | 0 | 2 | 6 | 8 | 9 | 11 | 14 | 16 | 17 | 19 | 19 | 21 | 24 |
不需要开二维数组,因为对每个物品操作时只需要上一次的数据,只需一维数组,而且01背包每个物品只有一个,只有取或不取的状态,为了能够保留上次的数据不被改变,所以从向前遍历
核心代码:
for(i=1;i<=m;i++){ //尝试放置每一个物品
for(j=t;j>=w[i];j--){//倒叙是为了保证每个物品都使用一次
f[j]=max(f[j-w[i]]+v[i],f[j]);
//在放入第i个物品前后,检验不同j承重量背包的总价值,如果放入第i个物品后比放入前的价值提高了,则修改j承重量背包的价值,否则不变
}
}
题目:
算法实验4:划分问题
Time Limit: 1 Sec Memory Limit: 128 MB Special Judge
Submit: 1852 Solved: 297
[Submit][Status][Forum]
Description
给定一个正整数的集合A={a1,a2,….,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10}
Input
第一行集合元素个数n n <=300 第二行n个整数
Output
如果能划分成两个集合,输出任意一个子集,否则输出“no”
Sample Input
5
1 3 8 4 10
Sample Output
3 10
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <cstdlib>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int dp[4000000];
int a[305];
int pre[4000000];
int main()
{
int n,sum=0;
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i],sum+=a[i];
if(sum%2!=0)
{
cout<<"no"<<endl;
return 0;
}
sum=sum/2;
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++) //
for(int j=sum; j>=a[i]; j--)
{
if(dp[j-a[i]]+a[i]>dp[j])
{
dp[j]=dp[j-a[i]]+a[i];
pre[j]=i;
}
}
int k,flag=0;
if(dp[sum]!=sum)
{
cout<<"no"<<endl;
return 0;
}
cout<<a[pre[sum]];
sum-=a[pre[sum]];
while(sum>0)
{
cout<<" "<<a[pre[sum]];
sum-=a[pre[sum]];
}
cout<<endl;
return 0;
}
完全背包
完全背包与01背包的区别是完全背包有n个物品,每个物品有无限件,容量是C,物品i的质量为w【i】,价值为v【i】,我们还是以二维数组来进行思考,我们在01背包中每次更新值时面临的决策是要不要放第i件物品,而在完全背包,我们需要抉择的便是,我们此时要放第i件物品几件?(0——最多能放的件数)
完全背包问题与01背包问题的区别在于完全背包每一件物品的数量都有无限个,而01背包每件物品数量只有1个,所以说与它相关的策略已经不是只有取和不取这两种策略了,而是有取0件、取1件、取2件……等等很多种策略
我们可以把把完全背包问题转化为01背包问题来解,第i种物品最多选V/c[i]件,于是可以把第i种物品转化为v/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。
状态转移方程:
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
每次也需要上一次的值,但是完全背包要正序遍历背包容量,确定在这件物品在比当前数小的容量的背包里已经放了几个了
核心代码:
for(int i=1; i<=n; i++)
for(int j=w[i]; j<=V; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序
f[j]=max(f[j],f[j-w[i]]+c[i]);
那么,01背包逆序的原因实际上是因为他要上一状态中的大的那一个,因此需要逆序保留原值,而完全背包要的是当前背包内的状态加上新的物品,因此需要正序。
多重背包
多重背包的每个物品都有自己的数量限制
多重背包可以采用二进制思想优化,比如有13个a物品,用二进制的思想,可以拆成
1个a物品
2个a物品
4个a物品
还剩6个a物品
各自捆绑,就相当于对这四个物品01背包
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 1000000
using namespace std;
int dp[MAX];//存储最后背包最大能存多少
int value[MAX],weight[MAX],number[MAX];//分别存的是物品的价值,每一个的重量以及数量
int bag;
void ZeroOnePack(int weight,int value )//01背包
{
int i;
for(i = bag; i>=weight; i--)
{
dp[i] = max(dp[i],dp[i-weight]+value);
}
}
void CompletePack(int weight,int value)//完全背包
{
int i;
for(i = weight; i<=bag; i++)
{
dp[i] = max(dp[i],dp[i-weight]+value);
}
}
void MultiplePack(int weight,int value,int number)//多重背包
{
if(bag<=number*weight)//如果总容量比这个物品的容量要小,那么这个物品可以直到取完,相当于完全背包
{
CompletePack(weight,value);
return ;
}
else//否则就将多重背包转化为01背包
{
int k = 1;
while(k<=number)
{
ZeroOnePack(k*weight,k*value);
number = number-k;
k = 2*k;//这里采用二进制思想
}
ZeroOnePack(number*weight,number*value);
}
}
int main()
{
int n;
while(~scanf("%d%d",&bag,&n))
{
int i,sum=0;
for(i = 0; i<n; i++)
{
scanf("%d",&number[i]);//输入数量
scanf("%d",&value[i]);//输入价值 此题没有物品的重量,可以理解为体积和价值相等
}
memset(dp,0,sizeof(dp));
for(i = 0; i<n; i++)
{
MultiplePack(value[i],value[i],number[i]);//调用多重背包,注意穿参的时候分别是重量,价值和数量
}
cout<<dp[bag]<<endl;
}
return 0;
}