提高组 (CSP-S) 进阶算法 / 第 3 章:动态规划进阶(树形 / 状压 / 数位 / 优化 DP)
📚 去课程闯关模式

动态规划核心代码模板汇总(CSP-J / CSP-S 考场速查)

本讲汇总了信息学竞赛(CSP-J / CSP-S / NOIP)中最常考的 7 类动态规划标准 C++ 实现模板,方便同学们在刷题与考前集中背诵和复盘。

1. 0-1 背包与完全背包一维滚动模板

核心区别在于容量循环方向:0-1 背包逆序枚举容量(防止同一物品被重复选),完全背包正序枚举容量(允许同一物品选无限次)。

// 0-1 背包:n 个物品,背包容量 m
for (int i = 1; i <= n; i++) {
    for (int j = m; j >= w[i]; j--) {
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    }
}

// 完全背包:每种物品可选无限次
for (int i = 1; i <= n; i++) {
    for (int j = w[i]; j <= m; j++) {
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    }
}

2. 多重背包(二进制拆分优化模板)

将数量为 c 的同种物品拆分为 1, 2, 4, ..., 2^k, 余数 组合成若干件独立的 0-1 背包物品,时间复杂度从 O(N*M*C) 降至 O(N*M*logC):

vector<pair<int, long long>> items; // {weight, value}
for (int i = 1; i <= n; i++) {
    int cnt = s[i];
    for (int k = 1; k <= cnt; k <<= 1) {
        items.push_back({k * w[i], 1LL * k * v[i]});
        cnt -= k;
    }
    if (cnt > 0) items.push_back({cnt * w[i], 1LL * cnt * v[i]});
}
for (auto &it : items) {
    for (int j = m; j >= it.first; j--) {
        dp[j] = max(dp[j], dp[j - it.first] + it.second);
    }
}

3. 区间 DP 标准区间枚举模板

先从小到大枚举区间长度 len,再枚举左端点 l,计算出右端点 r = l + len - 1,最后枚举分割点 k:

memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= n; i++) dp[i][i] = 0;

for (int len = 2; len <= n; len++) {
    for (int l = 1; l + len - 1 <= n; l++) {
        int r = l + len - 1;
        for (int k = l; k < r; k++) {
            dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + sum[r] - sum[l - 1]);
        }
    }
}

4. 树形 DP(树上背包与子树汇总模板)

先递归处理所有子节点 v,再利用子树结果更新当前节点 u。若做树上背包,注意利用当前已合并子树大小 sz[u] 进行上下界剪枝,保证整体复杂度严格 O(N*M):

void dfs(int u, int fa) {
    sz[u] = 1;
    dp[u][1] = val[u];
    for (int v : adj[u]) {
        if (v == fa) continue;
        dfs(v, u);
        for (int j = min(m, sz[u] + sz[v]); j >= 1; j--) {
            for (int k = max(1, j - sz[u]); k <= min(j - 1, sz[v]); k++) {
                dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
            }
        }
        sz[u] += sz[v];
    }
}

5. 状态压缩 DP(子集与状态转移模板)

// 枚举所有状态 mask,以及枚举 mask 的所有非空子集 sub
for (int mask = 0; mask < (1 << n); mask++) {
    for (int sub = mask; sub; sub = (sub - 1) & mask) {
        dp[mask] = min(dp[mask], dp[mask ^ sub] + cost[sub]);
    }
}

6. 数位 DP(记忆化搜索通用模板)

long long memo[20][200];
int a[20], len;

long long dfs(int pos, int state, bool lead, bool limit) {
    if (pos == 0) return 1;
    if (!limit && !lead && memo[pos][state] != -1) return memo[pos][state];
    int up = limit ? a[pos] : 9;
    long long res = 0;
    for (int d = 0; d <= up; d++) {
        res += dfs(pos - 1, state + d, lead && (d == 0), limit && (d == up));
    }
    if (!limit && !lead) memo[pos][state] = res;
    return res;
}

7. 单调队列优化 DP(滑动窗口最值转移模板)

deque<int> q;
q.push_back(0);
for (int i = 1; i <= n; i++) {
    while (!q.empty() && q.front() < i - k) q.pop_front();
    dp[i] = dp[q.front()] + a[i];
    while (!q.empty() && dp[q.back()] <= dp[i]) q.pop_back();
    q.push_back(i);
}
⬅ 上一讲
状态设计技巧
下一讲 ➡
二叉堆