c++ 练习6(dp)

1. 最大子段和

解题思路:

  • 初始化 currentSum 和 maxSum 为序列的第一个元素 a[0]currentSum 用于记录当前正在考虑的连续子段的和,maxSum 用于记录遍历过程中出现过的最大连续子段和。
  • 从序列的第二个元素开始(即 i = 1)进行遍历。在每次循环中,更新 currentSum 的值,它等于当前元素 a[i] 和 currentSum + a[i] 中的较大值。这意味着在当前位置,我们要么选择以当前元素开始一个新的连续子段(即取 a[i]),要么继续扩展之前的连续子段(即取 currentSum + a[i])。
  • 同时,使用 max 函数更新 maxSum,确保它始终记录到目前为止出现过的最大连续子段和.

代码:

#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 2e5 + 5;
int a[MAXN];

int main() {
    int n;
    // 读取序列的长度
    cin >> n;

    // 读取序列中的每个元素
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    // 初始化当前子段和与最大子段和为序列的第一个元素
    int currentSum = a[0];
    int maxSum = a[0];

    // 从第二个元素开始遍历序列
    for (int i = 1; i < n; ++i) {
        // 更新当前子段和,取当前元素和当前元素加上之前子段和的较大值
        currentSum = max(a[i], currentSum + a[i]);
        // 更新最大子段和
        maxSum = max(maxSum, currentSum);
    }

    // 输出最大子段和
    cout << maxSum << endl;

    return 0;
}

2. 采药

解题思路:

  • 定义 dp 数组,dp[j] 表示在时间 j 内可以获得的最大价值。
  • 外层循环遍历每一株草药,内层循环从总时间 T 开始倒序遍历到当前草药的采摘时间 timeCost[i]。这样做是为了确保每株草药只被使用一次。
  • 状态转移方程 dp[j] = max(dp[j], dp[j - timeCost[i]] + value[i]) 的含义是:对于时间 j,可以选择不采摘当前草药(即保持 dp[j] 不变),或者采摘当前草药(即 dp[j - timeCost[i]] + value[i]),取两者中的最大值作为 dp[j] 的新值。

代码:

#include <iostream>
#include <algorithm>
using namespace std;

const int MAX_T = 1005;
const int MAX_M = 105;

// 定义采摘每株草药的时间和价值数组
int timeCost[MAX_M];
int value[MAX_M];
// 定义动态规划数组,dp[i] 表示在时间 i 内可以获得的最大价值
int dp[MAX_T];

int main() {
    int T, M;
    // 读取总共能够用来采药的时间 T 和山洞里的草药的数目 M
    cin >> T >> M;

    // 读取每株草药的采摘时间和价值
    for (int i = 1; i <= M; ++i) {
        cin >> timeCost[i] >> value[i];
    }

    // 动态规划过程
    for (int i = 1; i <= M; ++i) {
        // 从总时间 T 开始倒序遍历,避免重复使用同一株草药
        for (int j = T; j >= timeCost[i]; --j) {
            // 状态转移方程,更新在时间 j 内的最大价值
            dp[j] = max(dp[j], dp[j - timeCost[i]] + value[i]);
        }
    }

    // 输出在规定时间 T 内可以采到的草药的最大总价值
    cout << dp[T] << endl;

    return 0;
}

3.宝物筛选

解题思路:

  • 定义 dp 数组,dp[j] 表示载重为 j 时的最大价值。
  • 遍历所有经过二进制优化后的物品,对于每个物品,从最大载重 W 开始倒序遍历到该物品的重量,使用状态转移方程 dp[j] = max(dp[j], dp[j - item.weight] + item.value) 更新 dp[j] 的值,确保不超过当前载重且价值最大。

代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 定义物品结构体,包含价值、重量
struct Item {
    int value;
    int weight;
};

int main() {
    int n, W;
    // 读取宝物种数 n 和采集车的最大载重 W
    cin >> n >> W;

    vector<Item> items;
    // 处理每种宝物
    for (int i = 0; i < n; ++i) {
        int v, w, m;
        // 读取每种宝物的价值 v、重量 w 和数量 m
        cin >> v >> w >> m;
        // 二进制优化
        for (int k = 1; k <= m; k *= 2) {
            items.push_back({k * v, k * w});
            m -= k;
        }
        if (m > 0) {
            items.push_back({m * v, m * w});
        }
    }

    // 动态规划数组,dp[j] 表示载重为 j 时的最大价值
    vector<int> dp(W + 1, 0);
    // 遍历所有物品
    for (const auto& item : items) {
        // 倒序遍历载重,避免重复使用同一物品
        for (int j = W; j >= item.weight; --j) {
            // 状态转移方程,更新最大价值
            dp[j] = max(dp[j], dp[j - item.weight] + item.value);
        }
    }

    // 输出载重为 W 时的最大价值
    cout << dp[W] << endl;

    return 0;
}

4.  最长公共子序列

解题思路:

创建一个大小为 (n + 1) x (n + 1) 的二维 std::vector 数组 dp,并初始化为全 0。dp[i][j] 表示 P1 的前 i 个元素和 P2 的前 j 个元素的最长公共子序列的长度。 使用两层嵌套的 for 循环遍历所有可能的 i 和 j。当 P1[i - 1] 等于 P2[j - 1] 时,说明当前元素是公共元素,dp[i][j] 的值等于 dp[i - 1][j - 1] 加 1。当 P1[i - 1] 不等于 P2[j - 1] 时,dp[i][j] 取 dp[i - 1][j] 和 dp[i][j - 1] 中的较大值。

代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 二分查找,找到第一个大于等于 x 的位置
int binarySearch(vector<int>& tail, int x) {
    int left = 0, right = tail.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (tail[mid] < x) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return left;
}

// 求最长上升子序列的长度
int lengthOfLIS(vector<int>& nums) {
    if (nums.empty()) return 0;
    vector<int> tail;
    tail.push_back(nums[0]);
    for (int i = 1; i < nums.size(); ++i) {
        if (nums[i] > tail.back()) {
            tail.push_back(nums[i]);
        } else {
            int pos = binarySearch(tail, nums[i]);
            tail[pos] = nums[i];
        }
    }
    return tail.size();
}

int main() {
    int n;
    cin >> n;

    vector<int> P1(n), P2(n);
    // 读取第一个排列 P1
    for (int i = 0; i < n; ++i) {
        cin >> P1[i];
    }
    // 读取第二个排列 P2
    for (int i = 0; i < n; ++i) {
        cin >> P2[i];
    }

    // 记录 P1 中每个数字的位置
    vector<int> pos(n + 1);
    for (int i = 0; i < n; ++i) {
        pos[P1[i]] = i;
    }

    // 将 P2 中每个数字替换为其在 P1 中的位置
    vector<int> newP2;
    for (int i = 0; i < n; ++i) {
        newP2.push_back(pos[P2[i]]);
    }

    // 求新序列的最长上升子序列的长度
    int result = lengthOfLIS(newP2);
    cout << result << endl;

    return 0;
}

学习总结:

  1. 求最长上升子序列的长度:

    int len = 0;
    for (int i = 0; i < n; ++i)
    {
    if (dp[len] < A[i])
    dp[++len] = A[i];
    else
    *lower_bound(dp + 1, dp + len + 1, A[i]) = A[i];
    }

求最长下降子序列长度:

*lower_bound(dp + 1, dp + len + 1, A[i], greater<int>()) = A[i];//greater<int>() 作为 lower_bound 的比较函数,改变了其默认的查找规则,使其能在降序序列中找到第一个不大于目标值的元素位置。

求最长不上升或不下降子序列的长度使用 upper_bound() 函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值