01背包的一些感悟

一、朴素的爆搜

01背包的普通爆搜版

//输入
4 5
2 3
1 2
3 4
2 2
//输出
7

每种情况选或不选。这里给出没看书前自己写的。

#include <iostream>
#include <stdio.h>
#include <vector>
void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
typedef pair<int, int> PII;
const int N = 1020;
PII a[N];

int maxn = 0;
int n, k;
void dfs(int u, int sum, int v) { //下标 总价值 总体积
    if (u == n)
    {
        if(v <= k)
            maxn = max(maxn, sum);
        return;
    }
    dfs(u + 1, sum + a[u].second, v + a[u].first);
    dfs(u + 1, sum, v);
}

int main()
{
    init();
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> a[i].first >> a[i].second; //weight val

    dfs(0, 0, 0);
    cout << maxn;
    return 0;
}

这是一点儿优化也没有的搜索,运行过程中极有可能会出现重复计算。

想到用数组记录状态。我一直认为dp能把数组的状态想对题目就做出来了一半。

按照书上的思路再写一遍dfs,这次的dfs减少了一个参数。将递归函数返回一个值。

依然是没有优化的爆搜,为之后的数组表示状态做准备。

#include <iostream>
#include <stdio.h>
#include <vector>
void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];

int maxn = 0;
int n, k;
int dfs(int u, int j) { //下标 剩余体积
    int res;
    if (u == n) { //搜不下去了
        res = 0;
    }
    else if (j < w[u]) { //放不下当前的
        res = dfs(u + 1, j);
    }
    else { //放得下当前的 两种情况都试下
        res = max(dfs(u + 1, j), dfs(u + 1, j - w[u]) + v[u]);
    }
    return res;
}

int main()
{
    init();
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //weight val

    cout << dfs(0, k);
    return 0;
}

二、记忆化搜索

然后我们加上记忆化数组dfs搜索。啥是记忆化数组?用数组记录某个状态、当程序需要用到这个状态时:如果有直接返回数字;如果没有更新。某种意义上也是拿空间换时间的一种策略。

这里的dfs参数有两个:下标和剩余体积。有没有可能用到之前出现过的状态呢?答案是有,因为dfs有回溯

用空间换时间后的代码如下(其实就是稍加改进,联想斐波那契数列的数组推导):

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N]; //体积 价值
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;
int dfs(int u, int j) { //下标 剩余体积
    if (dp[u][j] > 0) return dp[u][j];
    int res;
    if (u == n) { //搜不下去了
        res = 0;
    }
    else if (j < w[u]) { //放不下当前的
        res = dfs(u + 1, j);
    }
    else { //放得下当前的 两种情况都试下
        res = max(dfs(u + 1, j), dfs(u + 1, j - w[u]) + v[u]);
    }
    return dp[u][j] = res;
}

int main()
{
    init();
    memset(dp, -1, sizeof dp);
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //weight val

    cout << dfs(0, k);
    return 0;
}

改进版本的核心是数组表示状态。

用这个方式来表示下:令dp[i][j] 表示 从第 i 个物品开始挑选、总重量小于等于 j 时,总价值的最大值

然后我们写下代码:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i][j] 表示 从第i个物品开始挑选、总重量小于等于j时,总价值的最大值
void solve() {
    for (int i = n - 1; i >= 0; i--) {
        for (int j = 0; j <= k; j++) {
            if (j < w[i]) {
                dp[i][j] = dp[i + 1][j];
            }
            else {
                dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
            }
            //if (dp[i][j] > 0) cout << i << ' ' << j << endl;
        }
    }
}

int main()
{
    init();
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
    solve();
    cout << dp[0][k];
    return 0;
}

在上一个程序中我们逆着表示的。改变状态表示意义尝试下:令dp[i][j] 表示 挑选到第 i-1 个物品、总重量小于等于 j 时,总价值的最大值。

自己试着实现了下,用的偏移量防止数组访问下标 -1

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i][j] 表示 挑选到第 i-1 个物品、总重量小于等于 j 时,总价值的最大值
void solve() {
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= k; j++) {
            if (j < w[i-1]) { //i-1偏移量
                dp[i][j] = dp[i - 1][j];
            }
            else {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i-1]] + v[i-1]); //i-1是偏移量
            }
        }
    }
}

int main()
{
    init();
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
    solve();
    cout << dp[n][k];
    return 0;
}

同样的状态表示换一个推导方程:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i+1][j] 表示 选到 i, 总体积不超过 j
void solve() {
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j <= k; j++) {
            if (j < w[i]) {
                dp[i + 1][j] = dp[i][j];
            }
            else {
                dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
            }
        }
    }
}

int main()
{
    init();
    cin >> n >> k;
    for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
    solve();
    cout << dp[n][k];
    return 0;
}

三、实例

acwing周赛T3

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N = 220;
LL dp[N][N]; //dp[i+1][j]表示 选择i, 一共选了j 偏移一位
LL a[N];

int main()
{
    memset(dp, -0x3f, sizeof dp);
    int n, k, x; cin >> n >> k >> x; //k是窗口 选x个
    for (int i = 1; i <= n; i ++ ) cin >> a[i];

    dp[0][0] = 0;
    for (int i = 1; i <= n; i ++){
        for(int j = 1; j <= x; j ++){ //x是次数
            //再搞一个向前搜索
            for(int m = max(0, i - k); m < i; m ++){
                dp[i][j] = max(dp[i][j], dp[m][j-1] + a[i]);
            }
        }
    }

    LL res = -1;
    for(int i = n - k + 1; i <= n; i ++) res = max(res, dp[i][x]);
    cout << res;

    return 0;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇