C++DP动态规划全解

动态规划(DP)核心思想

动态规划通过将问题分解为子问题,存储子问题的解以避免重复计算,常用于优化递归问题。核心步骤包括定义状态、状态转移方程、初始条件和边界处理。


区间DP

区间DP用于解决涉及区间操作的问题,如合并石子、括号匹配等。通常状态定义为区间 [i, j] 的最优解,通过枚举分割点进行状态转移。

典型问题:合并石子最小代价

给定 n 堆石子排成一列,每次合并相邻两堆,代价为两者石子数之和,求最小总代价。

状态定义
dp[i][j] 表示合并区间 [i, j] 的最小代价。

状态转移方程
$$ dp[i][j] = \min_{k=i}^{j-1} (dp[i][k] + dp[k+1][j]) + \sum_{t=i}^{j} a_t $$

代码实现

int mergeStones(vector<int>& stones) {
    int n = stones.size();
    vector<int> prefix(n + 1, 0);
    for (int i = 0; i < n; ++i) 
        prefix[i + 1] = prefix[i] + stones[i];
    
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for (int len = 2; len <= n; ++len) { // 枚举区间长度
        for (int i = 0; i + len - 1 < n; ++i) {
            int j = i + len - 1;
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; ++k) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + prefix[j+1] - prefix[i]);
            }
        }
    }
    return dp[0][n - 1];
}


背包DP

背包问题分为 0-1 背包、完全背包和多重背包,核心在于状态定义和物品选取策略。

0-1 背包

每个物品只能选或不选,目标是在容量限制下最大化价值。

状态定义
dp[i][j] 表示前 i 个物品在容量 j 下的最大价值。

状态转移方程
$$ dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) $$

空间优化(一维数组)

int knapsack(vector<int>& weights, vector<int>& values, int capacity) {
    vector<int> dp(capacity + 1, 0);
    for (int i = 0; i < weights.size(); ++i) {
        for (int j = capacity; j >= weights[i]; --j) {
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }
    return dp[capacity];
}

完全背包

物品可无限选取,仅需将 0-1 背包的内层循环改为正序。

代码实现

int completeKnapsack(vector<int>& weights, vector<int>& values, int capacity) {
    vector<int> dp(capacity + 1, 0);
    for (int i = 0; i < weights.size(); ++i) {
        for (int j = weights[i]; j <= capacity; ++j) {
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }
    return dp[capacity];
}


树形DP

树形DP通过后序遍历处理子树状态,常用于树上路径或节点选择问题。

典型问题:二叉树中的最大路径和

路径可以是任意节点到任意节点的路径。

状态定义
dfs(node) 返回以 node 为起点的单侧最大路径和。

状态转移

  • 单侧最大和:max(node->val, node->val + max(left, right))
  • 全局最大和:max(current_max, node->val + left + right)

代码实现

struct TreeNode {
    int val;
    TreeNode *left, *right;
};

int maxPathSum(TreeNode* root) {
    int res = INT_MIN;
    function<int(TreeNode*)> dfs = [&](TreeNode* node) {
        if (!node) return 0;
        int left = max(dfs(node->left), 0);
        int right = max(dfs(node->right), 0);
        res = max(res, node->val + left + right);
        return node->val + max(left, right);
    };
    dfs(root);
    return res;
}


状态压缩DP

用于处理状态维度较高的场景(如网格、排列),通常用二进制表示状态。

典型问题:旅行商问题(TSP)

访问所有城市一次并返回起点的最短路径。

状态定义
dp[mask][i] 表示已访问城市集合 mask 且当前位于城市 i 的最短路径。

状态转移方程
$$ dp[mask][i] = \min_{j \notin mask} (dp[mask \oplus (1 << i)][j] + dist[j][i]) $$

代码实现

int tsp(vector<vector<int>>& dist) {
    int n = dist.size();
    vector<vector<int>> dp(1 << n, vector<int>(n, INT_MAX / 2));
    dp[1][0] = 0;
    for (int mask = 1; mask < (1 << n); ++mask) {
        for (int i = 0; i < n; ++i) {
            if (!(mask & (1 << i))) continue;
            for (int j = 0; j < n; ++j) {
                if (mask & (1 << j)) continue;
                dp[mask | (1 << j)][j] = min(dp[mask | (1 << j)][j], dp[mask][i] + dist[i][j]);
            }
        }
    }
    int res = INT_MAX;
    for (int i = 1; i < n; ++i) 
        res = min(res, dp[(1 << n) - 1][i] + dist[i][0]);
    return res;
}


数位DP

用于统计满足特定条件的数字数量,如不含某些数字或满足数位和条件。

典型问题:统计区间内不包含4的数字个数

状态定义
dp[pos][limit] 表示处理到第 pos 位时,受上限约束时的合法数字数量。

代码实现

int countNumbersWithout4(int n) {
    string s = to_string(n);
    int m = s.length();
    vector<vector<int>> dp(m, vector<int>(2, -1));
    function<int(int, bool)> dfs = [&](int pos, bool limit) {
        if (pos == m) return 1;
        if (dp[pos][limit] != -1) return dp[pos][limit];
        int up = limit ? s[pos] - '0' : 9;
        int res = 0;
        for (int d = 0; d <= up; ++d) {
            if (d == 4) continue;
            res += dfs(pos + 1, limit && (d == up));
        }
        return dp[pos][limit] = res;
    };
    return dfs(0, true);
}


以上内容覆盖了动态规划的主要类型及实现方法,适用于算法竞赛和面试准备。实际应用中需根据问题调整状态设计和转移逻辑。

给我点赞,点关注,给我点赞,点关注,给我点赞,点关注,给我点赞,点关注,给我点赞,点关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨染千千秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值