背包问题总结
01背包问题
//01背包问题——每件物品最多只用一次
/*
//二维动态规划
分析:
f[i][j] 表示只看前i个物品,总体积是j的情况下,总价值最大是多少。
result = max{f[n][0~V]} //答案
f[i][j]:
1. 不选第 i 个物品,f[i][j] = f[i-1][j]
2. 选第 i 个物品,f[i][j] = f[i-1][j-v[i]]+w[i]
f[i][j] = max{1. 2. }
f[0][0] = 0
时间复杂度:O(n^2)
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
//输入
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i];
//f[0][0]=0; // f属于全局变量,不需要再次初始化
//dp过程
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]);
}
////寻找答案 result = max{f[n][0~m]}
//int res=0;
//for(int i=0;i<=m;i++)
// res=max(res,f[n][i]);
//
////输出
//cout<<res<<endl;
//经分析,f[n][m]值最大
cout<<f[n][m]<<endl;
//结束
return 0;
}
*/
/*
//一维优化:
分析:
f[i]表示总体积是j的情况下,总价值最大是多少。
注意:
需要倒着来(参考:https://www.luogu.com.cn/problem/solution/P1048)
*/
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int f[N];
int v[N],w[N];
int main()
{
//输入
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i];
//f[0][0]=0; // f属于全局变量,不需要再次初始化
//dp过程
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]);
//经分析,f[m]值最大
cout<<f[m]<<endl;
//结束
return 0;
}
完全背包问题
// 完全背包——每件物品有无数个
/*
//朴素写法
分析:
动态规划:
1. 状态表示——f[i,j]
(1). 集合:所有只考虑前i个物品,且总体积不大于j的所有选法
(2). 属性:Max
2. 状态计算——集合的划分
方法:按第i个物品选了多少个划分(0,1,2,3,...,k-1,k)
(1). 选0个:f[i-1,j]
(2). 选k个:
曲线救国:
%1· 去掉k个物品i
%2· 求Max,f[i-1,j-k*v[i]]
%3· 再加回来k个物品i
f[i,j]=f[i-1,j-v[i]*k]+w[i]*k
代码:
//此算法最坏时间复杂度为:O(10^9)
//所以可能过不了(看评测机忙不忙hhh,偶尔能过)
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{
scanf("%d%d",&n,&m); //cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[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-v[i]*k]+w[i]*k); //找最大
cout<<f[n][m]<<endl;
return 0;
}
*/
/*
//优化:
分析:
f[i,j]=f[i-1,j-v[i]*k]+w[i]*k
f[i,j]=Max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2v]+2w , f[i-1,j-3v]+3w , ... )
f[i,j-v]=Max( f[i-1,j-v] , f[i-1,j-2v)+w , f[i-1,j-3v]+2w , ...... )
所以:
f[i,j]=Max(f[i-1,j],f[i,j-v]+w)
代码:
【如下】
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{
scanf("%d%d",&n,&m); //cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[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;
}
*/
/*
//一维实现(最优)
//过程类似与01背包
01背包:f[i,j]=Max(f[i-1,j],f[i-1,j-v]+w)
完全~: f[i,j]=Max(f[i-1,j],f[i,j-v]+w)
// 01~是i-1,完全~是i
代码:
【如下】
*/
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
scanf("%d%d",&n,&m); //cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]); //cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]); //不存在01背包问题,完全背包本来就是f[i][]
cout<<f[m]<<endl;
return 0;
}
注意:如果状态转移时,用的是上次循环的结果(即 f[i-1][j]
)去一维时应倒序,否则(即 f[i][j]
)去一维时应正序。
多重背包问题
朴素版本
//多重背包——每件物品有一个具体数量s[i]
/*
分析:
动态规划:
一、状态表示 f[i,j]
1. 集合:所有只从前i个物品中选,并且总体积不超过j的选法
2. 属性:Max
二、状态计算
方法:按第i个物品选多少个划分集合(0,1,2,3,...,s[i])
//与完全背包相似
状态转移方程:
f[i][j] = max(f[i-1][j-v[i]*k]+w[i]*k); (k=0,1,2,3,...,s[i])
朴素版本代码(暴力写法)://最坏时间复杂度O(n*m*s[i])
【如下】
*/
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];
int f[N][N];
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++) //k有两个条件
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); //取最大值
cout<<f[n][m]<<endl;
return 0;
}
优化版本
//多重背包优化版本(暴力版本将会TLE)
/*
分析:
原方程:f[i][j] = max(f[i-1][j-v[i]*k]+w[i]*k); (k=0,1,2,3,...,s[i])
不能以完全背包的方法进行优化(会多出一项)
二进制优化方式:
主要思想:
1. 将一个物品的数量按二进制的方式分为(log s[i])份(数量、价值、体积一起分)
最终将分为:1,2,4,8,...,2^k,c ( 2^(k+1)>s[i] , c<2^k )
2. 按01背包实现
【具体见视频hh】
代码:
【如下】
*/
#include <bits/stdc++.h>
using namespace std;
const int N=12010,M=2010; //物品数量需要上取整, N=1000*log(2000)=12000; M为体积(用于动态规划), M=2000
int n,m;
int v[N],w[N];
int f[M];
int main()
{
cin>>n>>m;
int cnt=0; //分组的组别
for(int i=1;i<=n;i++)
{
int a,b,s; //第i个物品的体积、价值和数量
cin>>a>>b>>s;
int k=1; //组别里面的个数,从1开始分
while(k<=s)
{
cnt++; //组别先增加
v[cnt]=a*k; //整体体积
w[cnt]=b*k; // 整体价值
s-=k; // s要减小
k*=2; // 组别里的个数增加(二进制2^k)
}
if(s>0) //剩余的一组 —— 剩下的s就是分析里的c
{
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;
}
分组背包问题
/*
分组背包 —— 每组物品有若干个,同一组内的物品最多只能选一个。
动态规划:
状态表示f[i,j]:
集合:只从前i组物品中选,且总体积不大于j的所有选法
属性:Max
状态计算:
f[i-1, j-v[i,k]] + w[i, k]
*/
// 本处直接给出一维优化代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 0; j < s[i]; j ++ )
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;
}