以下题目、题解思路等等均来自AcWing算法提高课,感兴趣的同学欢迎点击下方链接了解一下。
https://www.acwing.com/activity/content/introduction/16/
目录
一、01背包及各种变形
A、AcWing 423. 采药
01背包模板
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 50007;
int n, m;
int f[N];
int t[N], w[N];
int main()
{
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; ++ i)
{
scanf("%d%d", &t[i], &w[i]);
}
for(int i = 1; i <= n; ++ i)
for(int j = m; j >= t[i]; -- j)
f[j] = max(f[j], f[j - t[i]] + w[i]);
printf("%d\n", f[m]);
return 0;
}
B、AcWing 1024. 装箱问题
实际上可以直接看做01背包来做,题目要求求出最小的剩余空间,也就是要求出最大的可装体积
我们可以把体积看成是价值,这样问题就转变为了求最大价值。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 50007, INF = 0x3f3f3f3f;
int n, v;
int w[N];
int f[N];
int main()
{
cin >> v >> n;
for(int i = 1; i <= n; ++ i)
cin >> w[i];
for(int i = 1; i <= n; ++ i)
for(int j = v; j >= w[i]; -- j)
{
f[j] = max(f[j], f[j - w[i]] + w[i]);
}
cout << v - f[v] << endl;
}
C、AcWing 278. 数字组合(01背包模板求总方案数)
每个数只能选一次,01背包模板求总方案数。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 50007;
int n, m;
int f[N];
int a[N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n; ++ i){
cin >> a[i];
}
f[0] = 1;
for(int i = 1;i <= n; ++ i)
for(int j = m; j >= a[i]; -- j){
f[j] += f[j - a[i]];
}
cout << f[m] << endl;
return 0;
}
D、AcWing 1022. 宠物小精灵之收服(多维度01背包)
两个维度的权值,直接多加一维数组,多加一维循环即可。但是要注意因为多维度所以没办法直接输出答案,所以应该从大到小查找最优的答案,一旦被更新过就说明是待选答案
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 20007, M = 507, K = 107, INF = 0x3f3f3f3f;
int n, m, kk;
int f[M][K];
//f[i][j] : 表示收集了 j 个精灵 , 体力为 i , 用的最小的精灵球数量
int main()
{ memset(f, 0x3f, sizeof f);
f[0][0] = 0;
scanf("%d%d%d", &n, &m, &kk);
for(int i = 1, c, d; i <= kk; ++ i){
scanf("%d%d", &c, &d);
for(int j = m; j >= d; -- j){//体力
for(int k = kk; k >= 1; -- k)//精灵数
if(f[j - d][k - 1] + c <= n)//如果可以收得下
f[j][k] = min(f[j][k], f[j - d][k - 1] + c);//最少精灵球
}
}
for(int k = kk; k >= 0; -- k){
int ans = INF;
for(int j = 0; j < m; ++ j){
if(f[j][k] != INF && j < ans){
ans = j;
}
}
if(ans != INF){
printf("%d %d\n", k, m - ans);
return 0;
}
}
return 0;
}
E、AcWing 426. 开心的金明
//二维变量
//质量和价值,价值根据题意为质量*价格
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 50007;
int f[N];
int n, m;
int v[N], w[N];
int main(){
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; ++ i){
scanf("%d%d", &v[i], &w[i]);
w[i] *= v[i];
}
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]);
}
}
printf("%d\n", f[m]);
return 0;
}
二、完全背包
A、AcWing 1023. 买书(完全背包模板求方案数)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[] = {10, 20, 50, 100};
int n;
int f[1007];
int main(){
cin >> n;
f[0] = 1;
for(int i = 0; i < 4; ++ i){
for(int j = a[i]; j <= n; ++ j)
f[j] += f[j - a[i]];
}
cout << f[n] << endl;
return 0;
}
B、AcWing 1021. 货币系统(模板)
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 5007;
ll f[N];
int n, m;
int a[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++ i)
cin >> a[i];
f[0] = 1;
for(int i = 1; i <= n; ++ i){
for(int j = a[i]; j <= m; ++ j){
f[j] += f[j - a[i]];
}
}
cout << f[m] << endl;
return 0;
}
C、AcWing 532. 货币系统(思维)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 250007;
int n, m;
bool f[N];
int a[N];
int main(){
int t;
scanf("%d", &t);
while(t -- ){
scanf("%d", &n);
int ans = 0;
for(int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);//要排序,因为最小的肯定要留下来,因为只有小的才能凑出大的
int x = a[n];
memset(f, 0, sizeof f);
f[0] = true;//!
for(int i = 1; i <= n; ++ i){
if(!f[a[i]])ans ++ ;
for(int j = a[i]; j <= x; ++ j){
f[j] |= f[j - a[i]];
}
}
cout << ans << endl;
}
return 0;
}
三、多重背包
A、AcWing 4. 多重背包问题 I(模板)
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 100007;
int w[N], v[N];
int n, val;
int f[N];
int main(){
scanf("%d%d", &n, &val);
int cnt = 0;
for(int i = 1, a, b, c; i <= n; ++ i){
scanf("%d%d%d", &a, & b, &c);
for(int j = 1; j <= c; j <<= 1){
w[++ cnt ] = a * j;
v[cnt] = b * j;
c -= j;
}
if(c)w[++ cnt ] = a * c, v[cnt] = b * c;
}
for(int i = 1; i <= cnt; ++ i){
for(int j = val; j >= w[i]; -- j){
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
cout << f[val] << endl;
return 0;
}
B、AcWing 6. 多重背包问题 III
四、混合背包
AcWing 7. 混合背包问题(由多重背包修改得到)
直接按照多重背包做就好,把完全背包的无限量换成一个挺大的数就行了。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 100007;
int w[N], v[N];
int n, val;
int f[N];
int main(){
scanf("%d%d", &n, &val);
int cnt = 0;
for(int i = 1, a, b, c; i <= n; ++ i){
scanf("%d%d%d", &a, & b, &c);
if(c == -1)c = 1;
if(c == 0)c = 99999;
for(int j = 1; j <= c; j <<= 1){
w[++ cnt ] = a * j;
v[cnt] = b * j;
c -= j;
}
if(c)w[++ cnt ] = a * c, v[cnt] = b * c;
}
for(int i = 1; i <= cnt; ++ i){
for(int j = val; j >= w[i]; -- j){
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
cout << f[val] << endl;
return 0;
}
五、二维费用的背包问题
A、AcWing 8. 二维费用的背包问题
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 20005;
int n, V, M;
int f[N][N];
int v[N], m[N], w[N];
int main(){
cin >> n >> V >> M ;
for(int i = 1; i <= n; ++ i)
scanf("%d%d%d", &v[i], &m[i], &w[i]);
for(int i = 1; i <= n; ++ i){
for(int j = V; j >= v[i]; -- j)
for(int k = M; k >= m[i]; -- k){
f[j][k] = max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);
}
}
cout << f[V][M] << endl;
return 0;
}
B、AcWing 1020. 潜水员
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
/*f[i][j]表示氧气至少为i,氮气至少为j的最小重量*/
using namespace std;
const int N = 1007;
int n, m, k;
int a[N], b[N], w[N];
int f[N][N];
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= k; ++ i)
scanf("%d%d%d", &a[i], &b[i], &w[i]);
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int i = 1; i <= k; ++ i)
for(int j = n; j >= 0; -- j)
for(int l = m; l >= 0; -- l){
int a1 = a[i] + j;
if(a1 > n)a1 = n;
int a2 = b[i] + l;
if(a2 > m)a2 = m;
f[a1][a2] = min(f[a1][a2], f[j][l] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
六、分组背包
A、AcWing 1013. 机器分配(DP后查找最后的方案、倒着再来一遍)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 5007;
int n, m;
int f[N][N];
int w[N][N];
int way[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= m; ++ j)
scanf("%d", &w[i][j]);
for(int i = 1; i <= n; ++ i){
for(int j = 0; j <= m; ++ j){
for(int k = 0; k <= j; ++ k)
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
}
}
cout << f[n][m] << endl;
int j = m;
for(int i = n; i; -- i){//倒着再走一次,找到方案,因为DP的时候是没办法存方案的,DP的时候经常反悔,当前最优不一定是全局最优
for(int k = 0; k <= j; ++ k){
if(f[i][j] == f[i - 1][j - k] + w[i][k]){
way[i] = k;
j -= k;
break;
}
}
}
for(int i = 1; i <= n; ++ i){
printf("%d %d\n", i, way[i]);
}
return 0;
}
七、背包问题求方案数
A、AcWing 11. 背包问题求方案数
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 5007, INF = 0x3f3f3f3f, mod = 1e9 + 7;
int n, m;
int a[N];
int f[N];
int v[N], w[N];
int cnt[N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d%d", &v[i], &w[i]);
for(int i = 0; i <= m; ++ i)cnt[i] = 1;
for(int i = 1; i <= n; ++ i){
for(int j = m; j >= v[i]; -- j){
int val = f[j - v[i]] + w[i];
if(val > f[j]){
f[j] = val;
cnt[j] = cnt[j - v[i]];
}
else if(val == f[j]){
cnt[j] = (cnt[j] + cnt[j - v[i]] * 1ll) % mod;
}
}
}
printf("%d", cnt[m]);
return 0;
}
AcWing 12. 背包问题求具体方案
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 5007;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d%d", &v[i], &w[i]);
for(int i = n; i >= 1; -- i){
for(int j = m; j >= 0; -- 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]);
}
}
int j = m;
for(int i = 1; i <= n; ++ i){
if(j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]){
printf("%d ", i);
j -= v[i];
}
}
puts("");
return 0;
}