分组背包、完全背包

分组背包、完全背包

分组背包:多个物品分组,每组只能取1件。每一组的物品都可能性展开就可以了。时间复杂度 O(物品数量 * 背包容量)

完全背包:与 01 背包的区别仅在于每种商品可以选取无限次。时间复杂度 O(物品数量 * 背包容量)

P1757 通天之分组背包

  • 严格位置依赖的动态规划
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 时间复杂度 O(m * n)
int main() {
    // n 件物品,背包容量 m
    int m, n;
    cin >> m >> n;
    vector<vector<int>> arr(n + 1, vector<int>(3));
    for (int i = 1; i <= n; ++i)
        cin >> arr[i][0] >> arr[i][1] >> arr[i][2];
    // 根据组号排序
    sort(begin(arr), end(arr),
         [](vector<int> &v1, vector<int> &v2) {
             return v1[2] < v2[2];
         });

    // 组数
    int teams = 1;
    for (int i = 2; i <= n; ++i)
        if (arr[i][2] != arr[i - 1][2]) teams++;

    // dp[i][j] 表示从前面 i 个组中选取(每个组最多选一个),总重量不超过 j 时能获得的最大收益
    vector<vector<int>> dp(teams + 1, vector<int>(m + 1));
    // 首行为 0
    fill(begin(dp[0]), end(dp[0]), 0);
    for (int l = 1, r = 2, i = 1; l <= n; ++i) {
        // r 移动到下一组的开头
        while (r <= n && arr[l][2] == arr[r][2]) r++;
        // [l, r-1] 为当前组的物品
        for (int j = 0; j <= m; ++j) {
            // 一个都不选
            dp[i][j] = dp[i - 1][j];
            // 尝试选则组内的每一个
            for (int k = l; k < r; ++k)
                if (j - arr[k][0] >= 0)
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - arr[k][0]] + arr[k][1]);
        }
        // 处理下一组
        l = r++;
    }
    cout << dp[teams][m];
}
  • 空间优化
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 时间复杂度 O(m * n)
int main() {
    // n 件物品,背包容量 m
    int m, n;
    cin >> m >> n;
    vector<vector<int>> arr(n + 1, vector<int>(3));
    for (int i = 1; i <= n; ++i)
        cin >> arr[i][0] >> arr[i][1] >> arr[i][2];
    // 根据组号排序
    sort(begin(arr), end(arr),
         [](vector<int> &v1, vector<int> &v2) {
             return v1[2] < v2[2];
         });

    // 组数
    int teams = 1;
    for (int i = 2; i <= n; ++i)
        if (arr[i][2] != arr[i - 1][2]) teams++;

    // dp[i][j] 表示从前面 i 个组中选取(每个组最多选一个),总重量不超过 j 时能获得的最大收益
    // 首行为 0
    vector<int> dp(m + 1, 0);
    for (int l = 1, r = 2, i = 1; l <= n; ++i) {
        // r 移动到下一组的开头,[l, r-1] 为当前组的物品
        while (r <= n && arr[l][2] == arr[r][2]) r++;
        // 从右往左
        for (int j = m; j >= 0; --j) {
            // 一个都不选,或者尝试选则组内的每一个
            for (int k = l; k < r; ++k)
                if (j - arr[k][0] >= 0)
                    dp[j] = max(dp[j], dp[j - arr[k][0]] + arr[k][1]);
        }
        // 处理下一组
        l = r++;
    }
    cout << dp[m];
}

2218. 从栈中取出 K 个硬币的最大面值和

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int maxValueOfCoins(vector<vector<int>> &piles, int m) {
        // 组数
        int n = piles.size();
        // dp[i][j] 表示前 i 组上,一共拿走 j 个硬币的情况下,获得的最大价值
        vector<vector<int>> dp(n + 1, vector<int>(m + 1));
        // 组号从 1 开始
        for (int i = 1; i <= n; ++i) {
            // 当前组
            vector<int> team = piles[i - 1];
            // 计算前缀和
            int t = min((int) team.size(), m);
            vector<int> preSum(t + 1);
            for (int j = 1; j <= t; ++j)
                preSum[j] = preSum[j - 1] + team[j - 1];
            // 更新动态规划表
            for (int j = 0; j <= m; ++j) {
                // 当前组一个硬币也不拿
                dp[i][j] = dp[i - 1][j];
                // i 组里拿走前 k 个硬币的方案
                for (int k = 1; k <= min(t, j); ++k)
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - k] + preSum[k]);
            }
        }
        return dp[n][m];
    }
};
  • 空间优化
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int maxValueOfCoins(vector<vector<int>> &piles, int m) {
        // 组数
        int n = piles.size();
        // dp[i][j] 表示前 i 组上,一共拿走 j 个硬币的情况下,获得的最大价值
        // 首行全 0
        vector<int> dp(m + 1, 0);
        // 组号从 1 开始
        for (int i = 1; i <= n; ++i) {
            // 当前组
            vector<int> team = piles[i - 1];
            // 计算前缀和
            int t = min((int) team.size(), m);
            vector<int> preSum(t + 1);
            for (int j = 1; j <= t; ++j)
                preSum[j] = preSum[j - 1] + team[j - 1];
            // 更新动态规划表
            for (int j = m; j >= 0; j--) {
                // 当前组一个硬币也不拿
                // i 组里拿走前 k 个硬币的方案
                for (int k = 1; k <= min(t, j); ++k)
                    dp[j] = max(dp[j], dp[j - k] + preSum[k]);
            }
        }
        return dp[m];
    }
};

P1616 疯狂的采药

完全背包(模版)
给定一个正数 t,表示背包的容量
有 m 种货物,每种货物可以选择任意个
每种货物都有体积 costs[i] 和价值 values[i]
返回在不超过总容量的情况下,怎么挑选货物能达到价值最大
返回最大的价值

  • 严格位置依赖的动态规划
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

#define ll long long

int main() {
    int t, m;
    cin >> t >> m;
    vector<int> cost(m + 1);
    vector<int> value(m + 1);
    for (int i = 1; i <= m; ++i)
        cin >> cost[i] >> value[i];

    // dp[i][j] 表示前 i 号物品,每种可以无限拿,总代价不超过 j 的情况下,能获得的最大价值
    vector<vector<ll>> dp(m + 1, vector<ll>(t + 1));
    for (int i = 1; i <= m; ++i) {
        for (int j = 0; j <= t; ++j) {
            // 当前物品一个都不拿
            dp[i][j] = dp[i - 1][j];
            // 当前物品拿若干个
            if (j - cost[i] >= 0)
                dp[i][j] = max(dp[i][j], dp[i][j - cost[i]] + value[i]);
        }
    }
    cout << dp[m][t];
}
  • 空间压缩
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

#define ll long long

int main() {
    int t, m;
    cin >> t >> m;
    vector<int> cost(m + 1);
    vector<int> value(m + 1);
    for (int i = 1; i <= m; ++i)
        cin >> cost[i] >> value[i];

    // dp[i][j] 表示前 i 号物品,每种可以无限拿,总代价不超过 j 的情况下,能获得的最大价值
    // 首行全 0
    vector<ll> dp(t + 1, 0);
    for (int i = 1; i <= m; ++i) {
        // 从左往右
        for (int j = cost[i]; j <= t; ++j) {
            // 当前物品一个都不拿
            // 当前物品拿若干个
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
        }
    }
    cout << dp[t] << endl;
}

10. 正则表达式匹配

  • 暴力递归
#include <iostream>

using namespace std;

class Solution {
public:
    // s 从 i 下标开始能不能被 p 从 j 下标开始完全匹配出来
    bool recursive(string s, string p, int i, int j) {
        // 1. s 没后缀
        if (i == s.length()) {
            // 同时 p 也没后缀
            if (j == p.length()) return true;
            // p 还剩下一些后缀
            // 如果 p[j+1] 是 *,那么 p[j, j+1]可以消掉,然后看看再剩下的是不是都能消掉
            return j + 1 < p.length() && p[j + 1] == '*' && recursive(s, p, i, j + 2);
        }
        // 2. s 有后缀,p 没后缀
        if (j == p.length()) return false;
        // 3. 都有后缀
        // 3.1 j+1 位置不是 *
        if (j + 1 == p.length() || p[j + 1] != '*')
            return (s[i] == p[j] || p[j] == '.') && recursive(s, p, i + 1, j + 1);
        // 3.2 j+1 位置是 *
        // 完全背包
        // 选择1: 当前 p[j, j+1] 变成空,用 p 的 j+2 位置开始和 s 的 i 位置匹配
        bool p1 = recursive(s, p, i, j + 2);
        // 选择2: (s[i] == p[j] || p[j] == '.') 时,当前 p[j..j+1] 消去 s[i]
        bool p2 = (s[i] == p[j] || p[j] == '.') && recursive(s, p, i + 1, j);
        return p1 || p2;
    }

    bool isMatch(string s, string p) {
        return recursive(s, p, 0, 0);
    }
};
  • 记忆化搜索
#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    // s 从 i 下标开始能不能被 p 从 j 下标开始完全匹配出来
    bool recursive(string s, string p, int i, int j, vector<vector<int>> &dp) {
        if (dp[i][j] != 0) return dp[i][j] == 1;
        bool res;
        if (i == s.length()) {
            // 1. s 没后缀
            // 同时 p 也没后缀
            if (j == p.length()) {
                res = true;
            } else {
                // p 还剩下一些后缀
                // 如果 p[j+1] 是 *,那么 p[j, j+1]可以消掉,然后看看再剩下的是不是都能消掉
                res = j + 1 < p.length() && p[j + 1] == '*' && recursive(s, p, i, j + 2, dp);
            }
        } else if (j == p.length()) {
            // 2. s 有后缀,p 没后缀
            res = false;
        } else {
            if (j + 1 == p.length() || p[j + 1] != '*') {
                // 3. 都有后缀
                // 3.1 j+1 位置不是 *
                return (s[i] == p[j] || p[j] == '.') && recursive(s, p, i + 1, j + 1, dp);
            } else {
                // 3.2 j+1 位置是 *
                // 完全背包
                // 选择1: 当前 p[j, j+1] 变成空,用 p 的 j+2 位置开始和 s 的 i 位置匹配
                bool p1 = recursive(s, p, i, j + 2, dp);
                // 选择2: (s[i] == p[j] || p[j] == '.') 时,当前 p[j..j+1] 消去 s[i]
                bool p2 = (s[i] == p[j] || p[j] == '.') && recursive(s, p, i + 1, j, dp);
                res = p1 || p2;
            }
        }
        dp[i][j] = res ? 1 : 2;
        return res;
    }

    bool isMatch(string s, string p) {
        // 记忆化搜索
        // dp[i][j] == 0,表示没算过
        // dp[i][j] == 1,表示算过,答案是 true
        // dp[i][j] == 2,表示算过,答案是 false
        vector<vector<int>> dp(s.size() + 1, vector<int>(p.size() + 1, 0));
        return recursive(s, p, 0, 0, dp);
    }
};
  • 严格位置依赖的动态规划
#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    // 根据递归改写
    bool isMatch(string s, string p) {
        int n = s.length();
        int m = p.length();
        vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1));
        dp[n][m] = true;
        for (int j = m - 1; j >= 0; j--)
            dp[n][j] = j + 1 < m && p[j + 1] == '*' && dp[n][j + 2];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = m - 1; j >= 0; j--) {
                if (j + 1 == m || p[j + 1] != '*') {
                    dp[i][j] = (s[i] == p[j] || p[j] == '.') && dp[i + 1][j + 1];
                } else {
                    dp[i][j] = dp[i][j + 2] || ((s[i] == p[j] || p[j] == '.') && dp[i + 1][j]);
                }
            }
        }
        return dp[0][0];
    }
};

44. 通配符匹配

  • 暴力递归
#include <iostream>
#include <vector>

using namespace std;

// '?' 表示可以变成任意字符,数量 1 个
// '*' 表示可以匹配任何字符串
class Solution {
public:
    bool recursive(string s, string p, int i, int j) {
        // 1. s 没后缀
        if (i == s.length()) {
            // 同时 p 也没后缀
            if (j == p.length()) return true;
            // p 还剩下一些后缀
            // 如果 p[j] 是 *,那么 p[j]可以消掉,然后看看再剩下的是不是都能消掉
            return p[j] == '*' && recursive(s, p, i, j + 1);
        }
        // 2. s 有后缀,p 没后缀
        if (j == p.length()) return false;
        // 3. 都有后缀
        // 3.1 j 位置不是 *,那么当前的字符必须能匹配
        if (p[j] != '*') return (s[i] == p[j] || p[j] == '?') && recursive(s, p, i + 1, j + 1);
        // 3.2 j 位置是 *,可以选择消掉或者不消掉 s[i]
        return recursive(s, p, i + 1, j) || recursive(s, p, i, j + 1);
    }

    bool isMatch(string s, string p) {
        return recursive(s, p, 0, 0);
    }
};
  • 记忆化搜索
#include <iostream>
#include <vector>

using namespace std;

// '?' 表示可以变成任意字符,数量 1 个
// '*' 表示可以匹配任何字符串
class Solution {
public:
    bool recursive(string s, string p, int i, int j, vector<vector<int>> &dp) {
        if (dp[i][j] != 0) return dp[i][j] == 1;
        bool res;
        if (i == s.length()) {
            if (j == p.length()) {
                res = true;
            } else {
                res = p[j] == '*' && recursive(s, p, i, j + 1, dp);
            }
        } else if (j == p.length()) {
            res = false;
        } else {
            if (p[j] != '*') {
                res = (s[i] == p[j] || p[j] == '?') && recursive(s, p, i + 1, j + 1, dp);
            } else {
                res = recursive(s, p, i + 1, j, dp) || recursive(s, p, i, j + 1, dp);
            }
        }
        dp[i][j] = res ? 1 : 2;
        return res;
    }

    bool isMatch(string s, string p) {
        vector<vector<int>> dp(s.length() + 1, vector<int>(p.length() + 1));
        return recursive(s, p, 0, 0, dp);
    }
};
  • 严格位置依赖
#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.length();
        int m = p.length();
        vector<vector<bool>> dp(n + 1, vector<bool>(m + 1));
        dp[n][m] = true;
        for (int j = m - 1; j >= 0 && p[j] == '*'; j--)
            dp[n][j] = true;
        for (int i = n - 1; i >= 0; i--) {
            for (int j = m - 1; j >= 0; j--) {
                if (p[j] != '*') {
                    dp[i][j] = (s[i] == p[j] || p[j] == '?') && dp[i + 1][j + 1];
                } else {
                    dp[i][j] = dp[i + 1][j] || dp[i][j + 1];
                }
            }
        }
        return dp[0][0];
    }
};

P2918 [USACO08NOV] Buying Hay S

#include <iostream>
#include <vector>

using namespace std;

// 购买足量干草的最小花费
// 有 n 个提供干草的公司,每个公司都有两个信息
// cost[i] 代表购买 1 次产品需要花的钱
// val[i] 代表购买 1 次产品所获得的干草数量
// 每个公司的产品都可以购买任意次
// 你一定要至少购买 h 数量的干草,返回最少要花多少钱
int main() {
    int n, h;
    cin >> n >> h;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    int _max = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> value[i] >> cost[i];
        _max = max(_max, value[i]);
    }
    // 往外扩充
    int m = h + _max;
    // dp[i][j] 表示前 i 个公司里挑公司,购买严格 j 磅干草,需要的最少花费
    vector<vector<int>> dp(n + 1, vector<int>(m + 1));
    // 首行为最大值表示没有解
    fill(dp[0].begin() + 1, dp[0].end(), 0x7fffffff);
    // 除了 dp[0][0] 是 0
    dp[0][0] = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= m; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (j - value[i] >= 0 && dp[i][j - value[i]] != 0x7fffffff)
                dp[i][j] = min(dp[i][j], dp[i][j - value[i]] + cost[i]);
        }
    }
    int res = 0x7fffffff;
    // 至少购买 h 数量的干草,返回最少要花多少钱
    for (int j = h; j <= m; ++j)
        res = min(res, dp[n][j]);
    cout << res << endl;
}
  • 空间优化
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n, h;
    cin >> n >> h;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    int _max = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> value[i] >> cost[i];
        _max = max(_max, value[i]);
    }
    // 往外扩充
    int m = h + _max;
    // dp[i][j] 表示前 i 个公司里挑公司,购买严格 j 磅干草,需要的最少花费
    vector<int> dp(m + 1, 0x7fffffff);
    // 首行除了 dp[0][0] 是 0,其他都为最大值表示没有解
    dp[0] = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = value[i]; j <= m; ++j) {
            if (dp[j - value[i]] != 0x7fffffff)
                dp[j] = min(dp[j], dp[j - value[i]] + cost[i]);
        }
    }
    int res = 0x7fffffff;
    // 至少购买 h 数量的干草,返回最少要花多少钱
    for (int j = h; j <= m; ++j)
        res = min(res, dp[j]);
    cout << res << endl;
}
posted @ 2024-10-21 01:20  n1ce2cv  阅读(6)  评论(0编辑  收藏  举报