背包问题分析法
1 01背包问题
1.1 朴素做法
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//01背包问题 朴素做法
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N][N];//f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];//因为左边一定是存在的
if(j>=v[i])//假如背包容量还有
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//取两边的最大
}
cout<<f[n][m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
1.2 利用滚动数组优化做法 把第一维删除即可
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//01背包问题 滚动数组优化法
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N];//优化为一维,f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)//如果要用到上一维i-1的话,则从最大开始枚举
f[j]=max(f[j],f[j-v[i]]+w[i]);//取两边的最大
cout<<f[m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
2 完全背包问题
2.1 朴素做法 时间复杂度高,一般会TLE
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//完全背包问题,朴素做法
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N][N];//f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=j;k++)//选k个
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);//取两边的最大
cout<<f[n][m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
2.2 推理优化,降低时间复杂度
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//完全背包问题,优化后算法
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N][N];//f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];//右边肯定够装的,直接等于
if(j>=v[i])//假如背包还能装的下
f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);//取两边的最大
}
cout<<f[n][m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
2.3 滚动数组继续优化 把第一维删除即可
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//完全背包问题,滚动数组继续优化
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N];//优化为一维,f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)//如果只用到本维i的话,则从小的开始枚举
f[j]=max(f[j],f[j-v[i]]+w[i]);//取两边的最大
cout<<f[m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
3 多重背包问题
3.1 朴素做法与完全背包相同,只是多了个限定k<=s,而且很容易TLE
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//多重背包问题 朴素做法与完全背包相同,多了个数量限制
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];//v是体积,w是权重,s是数量个数
int f[N][N];//f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)//枚举可供选择的数
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+k*w[i]);//取两边的最大
cout<<f[n][m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
3.2 优化做法,优化成01背包问题,降低时间复杂度
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//多重背包问题 优化做法,优化成01背包问题
#include<bits/stdc++.h>
using namespace std;
const int N=25000;//因为每个数有2000个,而每个可以拆分为log2000=12个,所以得开24000个01背包,这样就拆分成选和不选的01背包了
int n,m;
int v[N],w[N];//v是体积,w是权重
int f[N];//01背包的优化 f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
int cnt=0;//当前背包数
for(int i=1;i<=n;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s)//下面进行拆分背包,按照二进制进行拆分背包
{
cnt++;//背包数加一
v[cnt]=a*k;//当前背包的体积
w[cnt]=b*k;//当前背包的权重
s-=k;//当前的个数减少
k*=2;//增加两倍
}
if(s>0)//假如还有剩余
{
cnt++;//背包数加一
v[cnt]=a*s;//最后拆分背包的体积
w[cnt]=b*s;//最后拆分背包的权重
}
}
n=cnt;//新的背包个数,也就是拆分后的个数
//下面就是01背包的做法
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);//取两边的最大
cout<<f[m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/
4 分组背包问题
/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//分组背包问题
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N],s[N];//v是体积,w是权重,s是数量
int f[N];//f是分析的f(i,j),表示前i个数选权重不超过j的最大值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];//读入第i个数的数量
for(int j=0;j<s[i];j++)//读入第i个数的所有物品的体积和权重
cin>>v[i][j]>>w[i][j];
}
for(int i=1;i<=n;i++)//根据状态计算来求
for(int j=m;j>=0;j--)
for(int k=0;k<s[i];k++)
if(v[i][k]<=j)//假如背包还有位置
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);//取两边的最大
cout<<f[m]<<endl;//输出结果
return 0;
}
/*到达胜利之前无法回头*/