文章目录
01背包问题
朴素算法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3+10;
int v[N]; //价值
int w[N]; //重量
int f[N][N]; //考虑前i个物品,背包容量为j时的最大价值
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if(j >= w[i])
f[i][j] = max(f[i-1][j],f[i-1][j-w[i]] + v[i]);
}
}
cout<<f[n][m];
}
一维优化
f[i][j]代表前i个物品 背包容量为j时的最大价值。
所以每读入一个价值 体积 就开始第二层即可。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i <= n; i ++)
{
int w,v; //分别代表物品的体积 价值
cin>>w>>v;
for(int j = m;j >= w; j--)
f[j] = max(f[j], f[j - w] + v);
}
cout<<f[m];
}
完全背包问题
每个物品用无数次
二维朴素算法
01背包问题中,第二层循环倒着的原因是
f[i][j] = max(f[i-1][j], f[i-1][j-w[i]+v[i])
计算f[i][j]时,需要用到f[i-1][j-w[i]]
优化成一维时,f[j-w[i]]在f]j]前面
所以如果从前往后循环,用的就是更新后的f[j-w[i]]即f[i][j-w[i]]而不是f[i-1][j-w[i]],也就是第i个物品用了多次。为了防止这种情况,01背包第二层循环倒过来
而完全背包正好迎合这种思想,故直接从前往后循环即可。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if(j >= w[i])
f[i][j] = max(f[i-1][j], f[i][j - w[i]] + v[i]);
}
cout<<f[n][m];
}
一维优化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)
{
f[j] = max(f[j], f[j-w[i]] + v[i]);
}
cout<<f[m];
}
多重背包问题I
最简单 直接三重for循环
第三层循环循环选0~s[i]个物品 //s[i]表示第i个物品的最大数量
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N];
int v[N],w[N],s[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i]>>s[i];
for(int i=1;i<=n;i++)
for(int j=m;j>= w[i];j--)
{
for(int k=0;k <= s[i];k ++)
{
if(j - k * w[i] >= 0)
f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
}
}
printf("%d",f[m]);
}
多重背包问题II
题解
数据范围上升至1e3, 3重for循环会109,故需要优化
二进制优化
结合自己的笔记看
如何将一个数x合理的划分为二进制数,使这些数可以表示 0~x 之间的任意一个数
对 10 而言
1 2 4 3 是合理的划分
对 12而言
1 2 4 5 是合理的划分
然后转化为01背包问题
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2010;
int n,m;
int f[N];
struct Good
{
int v,w;
};
vector<Good> goods;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int v,w,s;
cin >> v >> w >> s;
for(int k = 1; k <= s; k *= 2)
{
s -= k;
goods.push_back({v * k , w * k});
}
if(s > 0)
goods.push_back({v * s , w * s});
}
for(int i = 0 ; i < goods.size() ; i ++ )
{
for(int j = m ; j >= goods[i].v ;j --)
{
f[j] = max(f[j] , f[j - goods[i] . v ] + goods[i] . w);
}
}
cout<<f[m];
}
多重背包问题III
题解
需要用到单调队列优化。类似优先队列。
不太会,他也没大讲
混合背包问题
既有01背包只能用1次的 也有完全背包用无限次的 也有多重背包有个数限制的。
将多重背包的二进制转化为01背包。
所以只有01背包和完全背包的。
二者的区别在于01背包第二层for循环倒着,完全背包的第二层for循环正着,分开讨论
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1010;
int f[N];
struct thing
{
int kind;
int v,w;
};
vector <thing> things;
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int w,v,s;
cin>>w>>v>>s;
if(s == -1)
things.push_back({-1,v,w});
else if(s == 0)
things.push_back({0,v,w});
else //说明是多重背包问题
{
for(int k = 1; k <= s; k*=2)
{
s -= k;
things.push_back({-1, k * v, k * w});
}
if(s > 0) things.push_back({-1, s * v, s * w});
}
}
//01 和完全背包分开
for(int i = 0; i< things.size(); i++)
{
int kind = things[i].kind, v = things[i].v , w = things[i].w;
if(kind == -1)
{
for(int j = m; j >= things[i].w; j--) //如果是01背包就从大到小枚举
f[j] = max(f[j],f[j - w] + v);
}
else
{
for(int j = w; j <= m;j ++) //如果是完全背包就从小到大枚举
f[j] = max(f[j],f[j - w] + v);
}
}
cout<<f[m];
}
二维费用的背包问题
与01背包相比,既有体积限制,又有重量限制
f[i][j]表示背包体积为 i 最大承重为 j 情况下的能放的最大价值
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; //f[i][j] 表示 背包体积为i 承重为j的情况下的最大价值
int main()
{
int n,V,M;
cin>>n>>V>>M;
for(int i=1;i<=n;i++)
{
int v,m,w;//分别代表体积 重量 价值
cin>>v>>m>>w;
for(int j = V; j >= v; j--) //从大到小枚举
for(int k = M; k >= m; k--) //从大到小枚举
f[j][k] = max(f[j][k], f[j - v][k - m] + w);
}
cout<<f[V][M];
}
分组背包问题
有多个组,每个组内选1个最多。
数据范围很小 只有100 可以用O(n3)
是朴素多重背包算法的一般情况,代码很像
//是多重背包的一般情况。和多重背包代码很像很像
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N];
int v[N],w[N];//分别表示价值 体积
int main()
{
int n,m;//分别表示n组物品 背包容量为m
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int s; //表示每组中物品的个数 每输入一组接着求出来对于该组的方案
cin>>s;
for(int j = 1;j <= s;j++)
{
cin>>w[j]>>v[j];
}
for(int j = m; j >= 0; j--)
for(int k = 1; k <= s;k++)
{
if(j - w[k] >= 0)
f[j] = max(f[j], f[j - w[k]] + v[k]); //需要用到前面的f[j - w[k]] 所以j从后往前更新
}
}
cout<<f[m];
}
背包问题求方案数
需要很好的理解 什么是 状态转移
看f[i][j] 是从f[i-1][j]转移过来还是f[i-1][j-w[i]]+v[i]转移而来
/*
不能用自己最早的想法,先求一遍,求出f[n][m],再去重新求一遍,看看有多少个数 = f[n][m]
因为虽然数可能是一样的,但可能始终是一个方案,选的是同一组物品,只不过没有比他更大的,一直就是他延续下去的。
还是要看方案数,选不同的物品。
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
int n,m;
int f[N]; //f[i] 放入物品体积恰好为i情况下的最大价值
int g[N]; //g[i] 放入物品体积恰好为i的情况下的最大方案数是多少
int v[N],w[N]; //分别表示价值 体积
/*
f[i]表示体积恰好为i的情况下的最大价值 的原因:
因为01背包问题中,f[i]表示体积不超过i的情况下的最大价值,包含了0~i
因为这里是计数,所以 每个表示不同体积的 都要分开,要不会有重复
*/
int main()
{
cin>>n>>m;
g[0] = 1; //体积为0表示啥也不放 方案数为 1
for(int i=1;i<N;i++) f[i] = -INF;
for(int i=1;i<=n;i++)
{
int w,v;
cin>>w>>v;
for(int j = m; j >= w; j--)
{
int t = max(f[j],f[j-w] + v);
int s = 0;
if(t==f[j]) s+=g[j];
if(t==f[j-w] + v) s+=g[j-w];
s%=MOD;
f[j] =t;
g[j] = s;
}
}
//f[m]表示体积恰好为m情况下的最大价值 但是不一定代表体积小于等于m的情况 所以需要求出最大价值
int max_value = 0;
for(int i=1;i<=m;i++) max_value = max(max_value, f[i]);
int res = 0;
for(int i=0;i<=m;i++)
if(f[i] == max_value)
res = (res + g[i]) % MOD;
cout<<res;
}
背包问题求具体方案
题解
求dp的时候倒着,求方案的时候正着,然后如果前面和后面相等则输出前面的即代表字典序最小的。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
for(int i=n;i;i--) //从后往前 写dp 可以从前往后找方案,这样后面比前面大就可以直接输出前面
{
for(int j=0;j<=m;j++)
{
f[i][j] = f[i+1][j];
if(j >= w[i])
f[i][j] = max(f[i+1][j], f[i+1][j - w[i]] + v[i]);
}
}
int i=1,j=m;
while(i <= n)
{
if(j >= w[i] && f[i+1][j - w[i]] + v[i] >= f[i+1][j]) //if(j >= w[i] && f[i+1][j - w[i]] + v[i] == f[i][j])
{
printf("%d ",i);
j -= w[i];
i ++;
}
else i++;
}
}