dp - 背包模型

01背包

AcWing 2. 01背包问题(闫氏DP分析法)
在这里插入图片描述

二维板子

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n,V;
int v[N], w[N];
int f[N][N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> V; //物品个数 背包容量总数
    //朴素分析
    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];//体积 价值
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= V; ++j) {
            f[i][j] = f[i - 1][j];

            if (j >= w[i])
                f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }
    cout << f[n][V] << endl;
    return 0;
}

一维板子1

保证 f ≥ 0 f\ge0 f0 时, f j f_j fj 表示体积不超过 j j j 时的最大价值

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n,V;
int v[N], w[N];
int f[N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> V; //物品个数 背包容量总数
    
    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];//体积 价值
    }

    //01背包没办法在时间上优化 只能在空间上进行优化
    //发现f[i][j]的值只与i-1层有关 且f[i][j]=min(f[i-1][j],f[i-1][j-v[i]])
    //中可以看到f[i][j]更新时取得值 要么是上一层同样是j容量的位置 要么是更小的容量j-v[i]的位置
    //所以可以省掉第一位的空间 并且选择 从后往前滚动数组 达到空间上的优化

    for (int i = 1; i <= n; ++i) {
        for (int j = V; j >= v[i]; --j) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
	
    cout << f[V] << endl;
	return 0;
}

有些01背包问题里,最大价值可能为负数,此时 f j f_j fj 就不可以表示为体积不超过 j j j 时的最大价值了
可以通过打表,体会差别

此时的板子应该长这样

一维板子2

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int INF = 0x3f3f3f3f;
//注意 0x3f 0x3f3f 0x3f3f3f 0x3f3f3f3f 是四个不同的数

int n, V;
int v[N], w[N];

//差别1:f[j]表示体积为j下的最大价值 (少了'不超过')
int f[N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> V; //物品个数 背包容量总数

    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];//体积 价值
    }

    //差别2:初始化不同
    memset(f, -INF, sizeof(f));
    f[0] = 0;

    for (int i = 1; i <= n; ++i) {
        for (int j = V; j >= v[i]; --j) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

    //差别3:需要遍历f[] 找出其中的最优解
    int res = -INF;
    for (int i = 1; i <= V; ++i) {
        res = max(res, f[i]);
    }

    cout << res<< endl;
    return 0;
}

应用:


完全背包

AcWing 3. 完全背包问题(闫氏DP分析法)
在这里插入图片描述

一维板子

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n,V;
int v[N], w[N];
int f[N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> V; //物品个数 背包容量总数

    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];//体积 价值
    }

    //完全背包问题 只需要在01背包的基础上改为让j从小到大即可
    for (int i = 1; i <= n; ++i) {
        for (int j = v[i]; j <= V; ++j) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

多重背包

AcWing 4. 多重背包问题 I

每个物品s件,体积v,价值w

//核心代码
for (int i = 1; i <= n; ++i) {
    for (int j = V; j >= v; --j) {
        f[j] = max(f[j], f[j - v] + w, f[j - 2v] + 2w, ..., f[j - sv] + sw);
    }
}

普通板子

// O(n·m·s) 
// n=100 m=100 s=100

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m;
int v, w, s;
int f[N];//f[i]表示为体积不超过i的最大价值

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> v >> w >> s; // 体积 价值 个数
        for (int j = m; j >= v; --j) { //多重背包是01背包的扩展 所以是从大到小枚举的
            for (int k = 1; k <= s && k * v <= j; ++k) { // 0~s 总共s+1种决策
                f[j] = max(f[j], f[j - k * v] + k * w);
            }
        }
    }
    cout << f[m] << endl;
    return 0;
}

二进制优化版

AcWing 5. 多重背包问题 II
用最少的数表示1~x: ⌈ log ⁡ 2 ( x ) ⌉ \left\lceil\log_2(x)\right\rceil log2(x)
as:最少需要4个数:1、2、4、3 (不是8) 才能表示1~10所有的数

// O(n·log(s)·m) 
// n=1000 m=1000 s=2000

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m, K;
struct Good {
    int v, w;
};
int f[N];

int main() {
    ios::sync_with_stdio(0);

    vector<Good> goods;
    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拆开用最少的数表示 部分倍数就可以用01背包统计
            s -= k; //每次统计都需要-k
            goods.push_back({v * k, w * k});
        }
        if (s > 0)
            goods.push_back({v * s, w * s});
    }

    for (auto good:goods) {
        for (int j = m; j >= good.v; --j) {
            f[j] = max(f[j], f[j - good.v] + good.w);
        }
    }

    cout << f[m] << endl;
    return 0;
}

单调队列优化版

// O(nm)
// n=10000 m=20000 s=20000

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m, K;
int f[N], g[N];
int q[N];//单调队列 存的是最优解的体积 f[队首]为最优解
int main() {
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof(f));//更新上一层所有值

        for (int j = 0; j < v; ++j) { //枚举所有的余数 0~v-1
        	// j个单调队列
            int h = 0;//单调队列的首尾
            int t = -1;
            for (int k = j; k <= m; k += v) { //枚举所有余数相同的体积
                if (h <= t && (k - q[h]) / v > s) h++;// (当前体积-队首体积)/单份体积=倍数 要小于等于倍数限制s
                if (h <= t) f[k] = max(f[k], g[q[h]] + (k - q[h]) / v * w);// f[k]=max(f[k],f[j]+(k-j)/v*w);
                while (h <= t && g[q[t]] - (q[t] - j) / v * w <= g[k] - (k - j) / v * w) t--;
                q[++t] = k;
            }
        }
    }
    cout << f[m] << endl;
    /*
     * f[j]= max(f[j-v]+w, f[j-2v]+2w, f[j-3v]+3w.....)
     * 可以看到f[j]的值只和这些数有关 而这些数都有一个共同的特点:其下标%v的余数相同
     * 当统计f[j+v]的时候 可以发现 相关的数还是 f[j]+w, f[j-v]+2w, f[j-2v]+3w...
     * 这就像和滑动窗口有点像了
     *
     * 不过滑动窗口里的值是不会变的 f[j]在更新后会发生改变 这就需要一个方法来表示固定的值:
     * 原本:
     * f[j]    =     f[j]
     * f[j+v]  = max(f[j]+ w, f[j+v])
     * f[j+2v] = max(f[j]+2w, f[j+v]+w, f[j+2v])
     * 改成:
     * f[j]    =     f[j]
     * f[j+v]  = max(f[j], f[j+v]-w) + w
     * f[j+2v] = max(f[j], f[j+v]-w, f[j+2v]-2w) + 2w
     * 我们的f[]仍然表示最优解 并且也方便单调队列的内部排序
     * 
     * 根据 f[j+kv]-kw (也许可以叫做初始值) 来排最大值 最大值放在队首
     * */
    return 0;
}

混合背包

// n=1000 m=1000 v,w,s=1000

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, K;

struct Thing {
    int v, w, s;
};
vector<Thing> things;
int f[N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m; // 个数 最大容量
    for (int i = 1, v, w, s; i <= n; ++i) {
        cin >> v >> w >> s;// 体积 价值 个数
        if (s == -1 || s == 0)
            things.push_back({v, w, s}); //题目要求 s=-1 01背包 s=0 完全背包
        else {
            //二进制优化
            for (int k = 1; k <= s; k *= 2) {
                s -= k;
                things.push_back({v * k, w * k, -1}); //多重背包可以用01背包来做
            }
            if (s) things.push_back({v * s, w * s, -1});
        }
    }

    for (auto thing:things) {
        if (thing.s == -1) {//01背包
            for (int j = m; j >= thing.v; --j) {
                f[j] = max(f[j], f[j - thing.v] + thing.w);
            }

        } else {//完全背包
            for (int j = thing.v; j <= m; j++) {
                f[j] = max(f[j], f[j - thing.v] + thing.w);
            }
        }
    }

    cout << f[m] << endl;
    return 0;
}

二维费用的背包问题

#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 10;
int n, M, V;
int f[N][N];

int main() {
    ios::sync_with_stdio(0);
    cin >> n >> V >> M;
    for (int i = 1, v, w, m; i <= n; ++i) {
        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] << endl;
    return 0;
}

分组背包

每组物品有s种,同一组内的物品最多只能选一个
f i , j f_{i,j} fi,j 表示第 i i i 组在体积为 j j j 的情况下的最大价值

//核心代码
// f[i][j]表示第i组在体积为j的情况下的最大价值
// 可以优化为一维f[j]

for (int i = 1; i <= n; ++i) {
    for (int j = V; j >= 0; --j) {
        f[j] = max(f[j], f[j - v[1]] + w[1], f[j - v[2]] + w[2], ..., f[j - v[s]] + w[s]);
    }
}
// 与多重背包有些相似
// 多重背包是分组背包的一种特殊情况 特殊情况才有特殊的方法 分组背包只能循环

分组背包板子

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;

int n, m, K;
int f[N];
int w[N], v[N];

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m;
    for (int i = 1, s; i <= n; ++i) {
        cin >> s;
        for (int j = 1; j <= s; ++j) {
            cin >> v[j] >> w[j];
        }
        
        for (int j = m; j >= 0; --j) {
            for (int k = 1; k <= s; ++k) {
                if (j >= v[k])
                    f[j] = max(f[j], f[j - v[k]] + w[k]);
            }
        }
    }

    cout << f[m] << endl;
    return 0;
}

*

有依赖的背包问题

背包 + 树形dp

f i , j f_{i,j} fi,j 表示选节点 i i i 体积是 j j j 的情况下整颗子树的最大价值

// n=100 m=100 v,w=100

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;

int n, m, root;
int f[N][N];
// f[i][j]表示选节点i体积是j的情况下整颗 子树 的最大价值
// 与分组背包 f[i][j]每组i在体积为j的情况下的最大价值 类似
// 所以有依赖背包其实就是分组背包的变形
vector<int> e[N];//edge

int w[N], v[N];

void dfs(int u) {
    //先循环物品 再循环体积 最后循环决策
    
    for (int son:e[u]) {
        dfs(son);

        // 先更新儿子传上来的值
        // 此时u节点本身还没有加入进去 但是需要把u的位置空出来
        // 所以总体积从m-v[u]开始
        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[son][k]);
            }
        }
    }

    // 然后把u的值加进去
    for (int i = m; i >= v[u]; --i) {
        f[u][i] = f[u][i - v[u]] + w[u];
    }

    // 这一部分是没有加入u的 由于u的儿子依赖u
    // 如果u不选 儿子肯定不选 所以这一部分的f[][]直接清零
    for (int i = 0; i < v[u]; ++i) {
        f[u][i] = 0;
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for (int i = 1, p; i <= n; ++i) {
        cin >> v[i] >> w[i] >> p;
        if (p == -1)root = i;
        else {
            e[p].push_back(i);
        }
    }

    dfs(root);

    cout << f[root][m] << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值