背包九讲(大雪菜 & dd_engi)
大雪菜背包九讲1 大雪菜背包九讲2
dd_engi的背包九讲,emm...这个找不到原链接
背包问题 - 求最优值(一般)
01背包
- dp(i,j) :前i个物品,体积是j时的最大价值
- v(i),w(i);体积,价值数组:
- 对第i个物品,有选或者不选两种状态:dp(i)(j) = max(dp(i - 1, j),dp(i - 1,j - v(i)) + w(i));
- dp(0,0) = 0;
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++){//体积0-m
dp[i][j] = dp[i-1][j];//不选第i个
//剩余体积足够 -- 最大价值
if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[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]);
}
}*/
//res = f[m];
res = dp[n][m];//n个物品最大价值
cout << res;
完全背包
- f(i) 体积为i,最大价值(物品选择次数不限)
cin >> n >> m;
for(int i = 0; i < n; i++){
cin >> v >> w;
for(int j = v; j <= m; j++){
f[j] = max(f[j],f[j - v] + w);
}
}
cout << f[m];
多重背包1
- 每件物品只可以选择s次
- f(i) 体积为i,最大价值
优化
- 多重背包的二进制优化(每个物品分s份 -- 01 -- 二进制 2 ^ x = s log s 上取整)
struct node{
int v,w;
};
vector<node> arr;
cin >> n >> m;
for(int i = 0; i < n; i++){
cin >> v >> w >> s;
/*
for(int j = m; j >= 0; j--){
for(int k = 1; k <= s && k * v <= j; k++){
f[j] = max(f[j],f[j - k * v] + k * w);
}
} */
//二进制优化 -- 01背包
for(int k = 1; k <= s; k *= 2){
s -= k;
arr.push_back({v * k, w * k});
}
if(s > 0) arr.push_back({v * s, w * s});
}
//2
for(int i = 0; i < arr.size(); i++){
for(int j = m; j >= arr[i].v; j--){
f[j] = max(f[j],f[j - arr[i].v] + arr[i].w);
}
}
cout << f[m];
- 单调队列优化(以体积划分)
f[j] = max(f[j - v] + 2,f[j - 2 * v] + 2 * w,....f[j - k * v] + k * w)
f[j + v] = f[j] + w,f[j - v] + 2 * w
混合背包
- 物品分为x类,每类可以使用y次
- 分类使用01,完全,多重(二进制优化 - 01)
struct node{
int num,v,w;
};
vector<node> arr;
cin >> n >> m;
for(int i = 0; i < n; i++){
cin >> v >> w >> s;//s为背包类型
if(s < 0) arr.push_back({-1,v,w});//0 1
else if(s == 0) arr.push_back({0,v,w});//完全
else{
for(int k = 1; k <= s; k *= 2){//二进制优化多重背包 -- 01
s -= k;
arr.push_back({-1,v * k,w * k});
}
if(s > 0) arr.push_back({-1,v * s,w * s});
}
}
for(auto t : arr){
if(t.num < 0){
//01
for(int j = m; j >= t.v; j--){
f[j] = max(f[j],f[j - t.v] +t.w);//滚动数组,相当于默认 f[i][j] = f[i - 1][j]
}
}else{
for(int j = t.v; j <= m; j++){//v,小到大枚举,可多次使用
f[j] = max(f[j],f[j - t.v] + t.w);
}
}
}
cout << f[m];
二维费用背包
- 01背包多了一个参数,修改一下01即可
cin >> n >> v >> m;
for(int i = 0; i < n; i++){
int a,b,c;
cin >> a >> b >> c;
for(int j = v; j >= a; j--){
for(int k = m; k >= b; k--){
f[j][k] = max(f[j][k],f[j - a][k - b] + c);
}
}
}
cout << f[v][m];
分组背包
- 每个物品组(每组s个物品)中只可以选一个 - 01
cin >> n >> m;
for(int i = 0; i < n; i++){
cin >> s;
for(int j = 0; j < s; j++){
cin >> v[j] >> w[j];
}
for(int j = m; j >= 0; j--){
for(int k = 0; k < s; k++){
//每组s个物品
if(j >= v[k]) f[j] = max(f[j],f[j - v[k]] + w[k]);
}
}
}
cout << f[m];
有依赖的背包问题 - 树形DP与分组背包结合
- 选择物品i,必须选择i的依赖物品j
- i,j一 一对应:依赖关系组成了一棵树
- 物品 -- 体积 -- 决策
const int N = 105;
int root,n,m,p,idx,e[N],ne[N],h[N],v[N],w[N],f[N][N];//链式前向星存储图
//F[I][J] 选择第i个物品,在体积最大为j时de最大价值
//对每个节点,计算出它的子节点 -- 物品组
void add(int p,int i){
e[idx] = i;//v w 的 index
ne[idx] = h[p];
h[p] = idx++;//更新首元素
}
void dfs(int u){
//对当前节点u,寻找最大子节点
for(int i = h[u]; i != -1; i = ne[i]){//物品组
int cur = e[i];//子节点index
dfs(cur);
for(int j = m - v[u]; j >= 0; j--){//体积
for(int k = 0; k <= j; k++){//选择物品组中的某物品
f[u][j] = max(f[u][j],f[u][j - k] + f[cur][k]);
}
}
}
//必须先选择父节点
for(int i = m; i >= v[u]; i--){
f[u][i] = f[u][i - v[u]] + w[u];
}
//无法选择当前父节点 - 0
for(int i = 0; i < v[u]; i++){
f[u][i] = 0;
}
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);
memset(h,-1,sizeof(h));//
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i] >> p;
if(p == -1){
root = i;
}else{
add(p,i);//p --> i
}
}
dfs(root);
cout << f[root][m] << endl;
return 0;
}
最优方案数,最小总价值,最小总件数....
- 最(多)小总件数:(最多)最少放多少件物品可以装满背包 - min
- 字典序最小的最优方案:物品序号小-大枚举,若存在选择小序号的最优方案,则小序号必选
- 方案数:f(i,j)存储选择i个物品,最大体积为j的方案数
f[0][0] = 1
f[i][j] = f[i - 1][j] + f[i - 1][j - v[i]]
搜索解决背包问题
- 01背包
void dfs(i,cur_v,cur_w){
if(i > n){
if(cur_w > best) best = cur_w;
return;
}
if(cur_v + v[i] <= v){//选择此物品
dfs(i + 1, cur_v + v[i],cur_w + w[i]);
}
dfs(i + 1,cur_v,cur_w);
}