算法训练1-day30-动态规划

21 阅读3分钟
  1. 46. 携带研究材料(第六期模拟笔试)

AC代码:

#include <iostream>
#include <math.h>
#include <string>
#include <vector>

using namespace std;

int main() {
  int n, m;
  cin >> m >> n;
  // dp[i][j]:大小为i的背包存放0-j的材料,最大价值是多少
  vector<vector<int>> dp(n + 1, vector<int>(m, 0));
  vector<int> weight(m);
  vector<int> value(m);
  for (int i = 0; i < m; ++i) {
    cin >> weight[i];
  }
  for (int i = 0; i < m; ++i) {
    cin >> value[i];
  }

  // 放下标为0的物品
  for (int i = weight[0]; i <= n; ++i) {
    dp[i][0] = value[0];
  }
  for (int i = 1; i <= n; ++i) {
    for (int j = 0; j < m; ++j) {
	  // 当weight[j]大于当前背包容量,只能不放物品j,
	  // 因此背包容量为i时要放0-j的物品的价值就等于0-j-1的价值
      if (weight[j] > i) {
        dp[i][j] = dp[i][j - 1];
      } else {
        // 放下标为j的物品,或是不放,取最大值
        dp[i][j] = max(dp[i][j - 1], dp[i - weight[j]][j - 1] + value[j]);
      }
    }
  }
  cout << dp[n][m - 1] << endl;
  return 0;
}

滚动数组版本 注意:要先遍历物品,后遍历背包,这样,每个容量的背包才会计算一次物品j是否要存放进来;否则先遍历背包,后遍历物品,相当于每个容量的背包只会在所有物品中选一个存放 另外,容量要从大到小遍历,因为计算大小为i的背包放物品j的价值需要大小为i-1的背包不放物品j的价值,如果容量从小到大,那么前面的i-1大小的结果会先被覆盖掉

#include <iostream>
#include <math.h>
#include <string>
#include <vector>

using namespace std;

int main() {
  int n, m;
  cin >> m >> n;
  // dp[i][j]:大小为i的背包存放0-j的材料,最大价值是多少
  // vector<vector<int>> dp(n + 1, vector<int>(m, 0));
  vector<int> dp(n + 1);
  vector<int> weight(m);
  vector<int> value(m);
  for (int i = 0; i < m; ++i) {
    cin >> weight[i];
  }
  for (int i = 0; i < m; ++i) {
    cin >> value[i];
  }

  for (int j = 0; j < m; ++j) {
    for (int i = n; i >= weight[j]; --i) {
      // 放下标为j的物品,或是不放,取最大值
      dp[i] = max(dp[i], dp[i - weight[j]] + value[j]);
    }
  }

  cout << dp[n] << endl;
  return 0;
}
  1. 416. 分割等和子集

代码如下:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1)
            return false;
        int half = sum / 2;
        // 0-i的数是否能构成大小为j的总和
        vector<bool> dp(half + 1);
        dp[0] = true;
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = half; j >= nums[i]; --j) {
	            // 要么,延续之前的结果
	            // 要么,看当前容量j加进来nums[i]之前,
	            // 也就是0-(i-1)的数是不是能满足j - nums[i]
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[half];
    }
};

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        // 也可以使用库函数一步求和
        // int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1) return false;
        int target = sum / 2;

        // 开始 01背包
        for(int i = 0; i < nums.size(); i++) {
            for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target) return true;
        return false;
    }
};