ACM算法

POJ 动态规划 20 题完整题解 + 多解法对比 + 核心代码骨架。

如果你其实更想要 HDU 数据结构专题 或 Codeforces 构造/DP 专题,回复对应关键词即可换。

说明:代码为 C++17 核心骨架,省略主函数和部分输入输出;重点看状态、转移和多解法差异。


目录

编号 题目 类型

1 POJ 1014 Dividing 多重背包可行性

2 POJ 1742 Coins 多重部分和

3 POJ 1276 Cash Machine 多重背包最大可达

4 POJ 3017 Cut the Sequence 单调队列优化 DP

5 POJ 2373 Dividing the Path 单调队列优化 DP

6 POJ 1821 Fence 单调队列优化 DP

7 POJ 1159 Palindrome LCS / 区间 DP

8 POJ 3280 Cheapest Palindrome 区间 DP

9 POJ 2955 Brackets 区间 DP

10 POJ 1141 Brackets Sequence 区间 DP + 路径

11 POJ 1651 Multiplication Puzzle 区间 DP

12 POJ 1191 棋盘分割 区间 DP / 记忆化

13 POJ 1160 Post Office 区间 DP + 四边形不等式

14 POJ 3254 Corn Fields 状压 DP

15 POJ 1185 炮兵阵地 状压 DP

16 POJ 2411 Mondriaan's Dream 状压 / 轮廓线 DP

17 POJ 2282 The Counting Problem 数位 DP / 逐位统计

18 POJ 3252 Round Numbers 数位 DP / 组合

19 POJ 1088 滑雪 记忆化搜索 / 排序 DP

20 POJ 2342 Anniversary party 树形 DP


  1. POJ 1014 Dividing
    模型:有 6 种价值为 1~6 的物品,数量给出,问能否平分总价值。
    状态:fj 表示是否能凑出价值 j。
    多解法对比
    解法 思路 复杂度 特点
    二进制拆分 + 01 背包 把数量拆成 1,2,4,... O(sum log cnt) 最好写
    DFS + 剪枝 从大到小搜索 实际很快 数据强时需剪枝
    单调队列多重背包 按余数分类 O(6 * sum) 最稳但难写
    核心代码:二进制拆分
    cpp
    bool fMAXN;
    vector w;
    for (int v = 1; v <= 6; v++) {
    int c; cin >> c;
    for (int k = 1; k <= c; k <<= 1) {
    w.push_back(k * v);
    c -= k;
    }
    if © w.push_back(c * v);
    }
    f0 = true;
    for (int x : w)
    for (int j = sum; j >= x; j--)
    fj |= fj - x;
    if (sum % 2 == 0 && fsum / 2) cout << "Can be divided.\n";

  1. POJ 1742 Coins
    模型:多重部分和,问 1~m 中有多少值能被凑出。
    状态:fj 是否可达;usedj 当前硬币已用数量。
    多解法对比
    解法 复杂度 特点
    used 数组 O(nm) 最经典,必须掌握
    二进制 + bitset O(nm/word) 常数小
    单调队列 O(nm) 理论最优,代码复杂
    核心代码:used 数组
    cpp
    bool fMAXM;
    int usedMAXM;

f0 = true;

int ans = 0;

for (int i = 1; i <= n; i++) {

int a, c; cin >> a >> c;

memset(used, 0, sizeof(int) * (m + 1));

for (int j = a; j <= m; j++) {

if (!fj && fj - a && usedj - a < c) {

fj = true;

usedj = usedj - a + 1;

ans++;

}

}

}

cout << ans << '\n';


  1. POJ 1276 Cash Machine
    模型:多重背包,容量 cash,每种面额有数量,求最大可达金额。
    多解法对比
    解法 复杂度 特点
    二进制拆分 + 01 背包 O(sum log cnt) 推荐
    单调队列优化 O(n * cash) 更快但难写
    核心代码
    cpp
    bool fMAXC;
    f0 = true;
    for (int i = 1; i <= n; i++) {
    int v, c; cin >> c >> v;
    for (int k = 1; k <= c; k <<= 1) {
    int x = k * v;
    for (int j = cash; j >= x; j--)
    fj |= fj - x;
    c -= k;
    }
    if © {
    int x = c * v;
    for (int j = cash; j >= x; j--)
    fj |= fj - x;
    }
    }
    for (int j = cash; j >= 0; j--)
    if (fj) { cout << j << '\n'; break; }

  1. POJ 3017 Cut the Sequence

    模型:把序列分成若干段,每段和不超过 M,最小化每段最大值之和。

    状态:

    text

    dpi = min(dpj + max(aj+1...i)),且 sum(j+1...i) <= M

    多解法对比

    解法 复杂度 特点

    暴力 DP O(n^2) 只能小数据

    单调队列 + multiset O(n log n) 标准解

    线段树维护 O(n log n) 更直观

    核心思路

  2. 用双指针维护 sum <= M 的最左合法 j。

  3. 单调队列维护 a 的递减序列。

  4. 队列中每个元素代表一段 j,该段内 max(aj+1...i) 固定。

  5. 用 multiset 维护候选 dpj + max。

    伪代码

    cpp

    deque q;

    multiset st;

    int L = 1, sum = 0;

    for (int i = 1; i <= n; i++) {

    sum += ai;

    while (sum > M) sum -= aL++;

    while (!q.empty() && aq.back() <= ai) {

    // 删除旧候选

    q.pop_back();

    }

    q.push_back(i);

    // 删除越界队头

    while (!q.empty() && q.front() < L) q.pop_front();

    // 更新候选,具体实现需维护每个队头对应的最优 dp

    dpi = *st.begin();

    }


  1. POJ 2373 Dividing the Path
    模型:给区间 0, L 喷水,每个喷头覆盖长度 2A, 2B 且为偶数,不能覆盖牛的区域,求最少喷头数。
    状态:
    text
    dpi = min(dpj + 1)
    i - 2B <= j <= i - 2A
    i, j 为偶数
    j, i 不覆盖牛区
    多解法对比
    解法 复杂度 特点
    暴力枚举 j O(L * B) 超时
    单调队列 O(L) 标准解
    核心代码
    cpp
    for (int i = 2; i <= L; i += 2) {
    if (badi) continue;
    while (!q.empty() && q.front() < i - 2 * B) q.pop_front();
    if (i - 2 * A >= 0) {
    int j = i - 2 * A;
    if (dpj != INF) {
    while (!q.empty() && dpq.back() >= dpj) q.pop_back();
    q.push_back(j);
    }
    }
    if (!q.empty()) dpi = dpq.front() + 1;
    }

  1. POJ 1821 Fence

    模型:K 个工人刷墙,每个工人必须刷包含位置 s 的连续段,长度不超过 L,每格收益 p,求最大收益。

    状态:

    text

    dpij = 前 i 个工人刷前 j 块的最大收益

    转移

    不刷:

    text

    dpij = dpi-1j

    第 i 个工人刷 k+1...j:

    text

    dpij = max(dpi-1k + p_i * (j - k))

    k < s_i <= j

    j - k <= L_i

    多解法对比

    解法 复杂度 特点

    暴力枚举 k O(K N^2) 超时

    单调队列优化 O(K N) 标准

    核心代码

    cpp

    for (int i = 1; i <= K; i++) {

    deque q;

    for (int j = 0; j <= N; j++) {

    dpij = dpi-1j;

    if (j) dpij = max(dpij, dpij-1);

    复制代码
     if (j >= s[i]) {
         int k = j - L[i];
         if (k >= 0) {
             while (!q.empty() && dp[i-1][q.back()] - p[i] * q.back()
                    <= dp[i-1][k] - p[i] * k)
                 q.pop_back();
             q.push_back(k);
         }
         while (!q.empty() && q.front() < j - L[i]) q.pop_front();
         if (!q.empty())
             dp[i][j] = max(dp[i][j],
                 dp[i-1][q.front()] + p[i] * (j - q.front()));
     }

    }

    }


  1. POJ 1159 Palindrome
    模型:最少插入多少字符使字符串变回文。
    核心结论:
    text
    答案 = n - LCS(s, reverse(s))
    多解法对比
    解法 复杂度 空间
    LCS + 滚动数组 O(n^2) O(n)
    区间 DP O(n^2) O(n^2)
    哈希 + 二分 O(n log n) O(n)
    核心代码:LCS 滚动数组
    cpp
    string t = s;
    reverse(t.begin(), t.end());
    vector dp(n + 1), ndp(n + 1);
    for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
    if (si-1 == tj-1) ndpj = dpj-1 + 1;
    else ndpj = max(dpj, ndpj-1);
    }
    swap(dp, ndp);
    }
    cout << n - dpn << '\n';

  1. POJ 3280 Cheapest Palindrome
    模型:给定字符串,插入或删除字符有代价,求变回文最小代价。
    状态:
    text
    dpij = si...j 变回文最小代价
    转移
    若 si == sj:
    text
    dpij = dpi+1j-1
    否则:
    text
    dpij = min(
    dpi+1j + costs\[i],
    dpij-1 + costs\[j]
    )
    其中 costc = min(addc, delc)。
    核心代码
    cpp
    for (int len = 2; len <= n; len++) {
    for (int i = 0; i + len - 1 < n; i++) {
    int j = i + len - 1;
    if (si == sj) dpij = dpi+1j-1;
    else dpij = min(dpi+1j + costs\[i],
    dpij-1 + costs\[j]);
    }
    }
    cout << dp0n-1 << '\n';

  1. POJ 2955 Brackets
    模型:最长合法括号子序列长度。
    状态:
    text
    dpij = si...j 中最长合法括号子序列长度
    转移
    若 si 与 sj 匹配:
    text
    dpij = max(dpij, dpi+1j-1 + 2)
    枚举分割点:
    text
    dpij = max(dpij, dpik + dpk+1j)
    核心代码
    cpp
    for (int len = 2; len <= n; len++) {
    for (int i = 0; i + len - 1 < n; i++) {
    int j = i + len - 1;
    if ((si'(' && sj')') || (si'' \&\& s\[j']'))
    dpij = dpi+1j-1 + 2;
    for (int k = i; k < j; k++)
    dpij = max(dpij, dpik + dpk+1j);
    }
    }
    cout << dp0n-1 << '\n';

  1. POJ 1141 Brackets Sequence
    模型:输出最短合法括号序列。
    状态:
    text
    dpij = si...j 最少需要添加的括号数
    pathij = 分割点或匹配标记
    转移
    若匹配:
    text
    dpij = dpi+1j-1
    否则枚举分割:
    text
    dpij = min(dpik + dpk+1j)
    核心代码
    cpp
    void print(int i, int j) {
    if (i > j) return;
    if (i == j) {
    if (si'(' || si')') cout << "()";
    else cout << "\[\]";
    return;
    }
    if (pathij == -1) {
    cout << si;
    print(i+1, j-1);
    cout << sj;
    } else {
    int k = pathij;
    print(i, k);
    print(k+1, j);
    }
    }

  1. POJ 1651 Multiplication Puzzle
    模型:取牌,每次取中间牌得分 ai*ak*aj,求最小总得分。
    状态:
    text
    dpij = 将 i...j 取完只剩 i,j 的最小代价
    转移
    text
    dpij = min(dpik + dpkj + ai*ak*aj)
    核心代码
    cpp
    for (int len = 2; len < n; len++) {
    for (int i = 0; i + len < n; i++) {
    int j = i + len;
    dpij = INF;
    for (int k = i + 1; k < j; k++)
    dpij = min(dpij,
    dpik + dpkj + ai*ak*aj);
    }
    }
    cout << dp0n-1 << '\n';

  1. POJ 1191 棋盘分割
    模型:8×8 棋盘切 n 块,最小化均方差。
    状态:
    text
    dpkx1y1x2y2 = 切 k 块的最小平方和
    转移:枚举横切、竖切。
    核心代码骨架
    cpp
    int dfs(int k, int x1, int y1, int x2, int y2) {
    if (dpkx1y1x2y2 != -1) return dpk...;
    if (k == 1) return sum2(x1,y1,x2,y2);
    int ans = INF;
    for (int x = x1; x < x2; x++) {
    ans = min(ans, dfs(k-1, x1,y1,x,x2? ...) + sum2(...));
    ans = min(ans, dfs(k-1, x+1,y1,x2,y2) + sum2(...));
    }
    // 同理竖切
    return dpk... = ans;
    }
    最终答案:
    text
    sqrt(dpn1188 / n - avg^2)

  1. POJ 1160 Post Office
    模型:V 个村庄建 P 个邮局,最小化总距离。
    状态:
    text
    dpij = 前 i 个村建 j 个邮局的最小代价
    costij = 在 i...j 建一个邮局的最小距离和
    转移
    text
    dpij = min(dpkj-1 + costk+1i)
    多解法对比
    解法 复杂度 特点
    普通区间 DP O(P V^2) 可过
    四边形不等式 O(P V) 进阶
    核心代码
    cpp
    for (int i = 1; i <= V; i++)
    dpi1 = cost1i;

for (int j = 2; j <= P; j++)

for (int i = j; i <= V; i++)

for (int k = j - 1; k < i; k++)

dpij = min(dpij, dpkj-1 + costk+1i);


  1. POJ 3254 Corn Fields
    模型:网格中选格子,相邻不能同时选,求方案数。
    状态:
    text
    dprowstate = 第 row 行状态为 state 的方案数
    合法状态:
    text
    !(state & (state << 1))
    核心代码
    cpp
    for (int s = 0; s < (1<<n); s++)
    if (!(s & (s<<1))) oks = true;

dp00 = 1;

for (int r = 1; r <= m; r++) {

for (int s = 0; s < (1<<n); s++) {

if (!oks || (s & fieldr)) continue;

for (int ps = 0; ps < (1<<n); ps++) {

if (s & ps) continue;

dprs = (dprs + dpr-1ps) % MOD;

}

}

}


  1. POJ 1185 炮兵阵地
    模型:炮兵不能攻击到上下左右两格内的其他炮兵。
    合法状态:
    text
    !(s & (s<<1)) && !(s & (s<<2))
    状态:
    text
    dprowcurprev
    核心代码骨架
    cpp
    for (int r = 1; r <= n; r++) {
    for (int cur : states) {
    if (cur & fieldr) continue;
    for (int prev : states) {
    if (cur & prev) continue;
    for (int pprev : states) {
    if ((cur & pprev) || (prev & pprev)) continue;
    dprcurprev = max(dprcurprev,
    dpr-1prevpprev + popcount(cur));
    }
    }
    }
    }

  1. POJ 2411 Mondriaan's Dream
    模型:用 1×2 骨牌铺满 H×W,求方案数。
    多解法对比
    解法 复杂度 特点
    按行状压 DP O(H * 2^W * 合法转移) 经典
    轮廓线 DP O(HW * 2^W) 更通用
    核心代码:按行 DP
    cpp
    void dfs(int col, int cur, int nxt) {
    if (col == W) {
    transcur.push_back(nxt);
    return;
    }
    if (cur & (1<<col)) {
    dfs(col+1, cur, nxt);
    } else {
    // 横放
    if (col+1 < W && !(cur & (1<<(col+1))))
    dfs(col+2, cur, nxt);
    // 竖放
    dfs(col+1, cur, nxt | (1<<col));
    }
    }

dp00 = 1;

for (int r = 0; r < H; r++)

for (int s = 0; s < (1<<W); s++)

for (int ns : transs)

dpr+1ns += dprs;

cout << dpH0 << '\n';


  1. POJ 2282 The Counting Problem

    模型:统计 a,b 中数字 0~9 出现次数。

    多解法对比

    解法 复杂度 特点

    逐位统计 O(10 log n) 数学强

    数位 DP O(10 * log n * 状态) 通用

    逐位统计核心

    cpp

    long long countDigit(long long n, int d) {

    long long cnt = 0;

    for (long long base = 1; base <= n; base *= 10) {

    long long high = n / (base * 10);

    long long cur = (n / base) % 10;

    long long low = n % base;

    复制代码
    if (d == 0) {
        if (high == 0) continue;
        high--;
        cnt += high * base + low + 1;
        high++;
        cnt -= base;
        // 实际需按公式调整,建议直接写数位 DP
    } else {
        if (cur > d) cnt += (high + 1) * base;
        else if (cur == d) cnt += high * base + low + 1;
        else cnt += high * base;
    }

    }

    return cnt;

    }

    0 的处理容易错,比赛建议直接写数位 DP。


  1. POJ 3252 Round Numbers

    模型:统计 a,b 中二进制 0 的个数不少于 1 的个数。

    状态:

    text

    dpposcnt0cnt1leadlimit

    核心代码骨架

    cpp

    int dfs(int pos, int c0, int c1, bool lead, bool limit) {

    if (pos == -1) return c0 >= c1;

    if (!limit && !lead && dpposc0c1 != -1)

    return dpposc0c1;

    int up = limit ? bitpos : 1;

    int ans = 0;

    for (int x = 0; x <= up; x++) {

    if (lead && x == 0)

    ans += dfs(pos-1, 0, 0, true, limit && x == up);

    else

    ans += dfs(pos-1, c0 + (x == 0), c1 + (x == 1),

    false, limit && x == up);

    }

    if (!limit && !lead) dpposc0c1 = ans;

    return ans;

    }

    答案:

    text

    solve(b) - solve(a-1)


  1. POJ 1088 滑雪
    模型:网格中找最长下降路径。
    多解法对比
    解法 复杂度 特点
    记忆化搜索 O(RC) 最好写
    排序 + DP O(RC log RC) 无递归
    核心代码:记忆化
    cpp
    int dfs(int x, int y) {
    if (dpxy) return dpxy;
    dpxy = 1;
    for (int d = 0; d < 4; d++) {
    int nx = x + dxd, ny = y + dyd;
    if (in(nx,ny) && anxny < axy)
    dpxy = max(dpxy, dfs(nx,ny) + 1);
    }
    return dpxy;
    }

  1. POJ 2342 Anniversary party
    模型:树形 DP,员工不能和直接上司同时选,最大快乐值。
    状态:
    text
    dpu0 = 不选 u 的最大值
    dpu1 = 选 u 的最大值
    转移
    text
    dpu0 = sum(max(dpv0, dpv1))
    dpu1 = valu + sum(dpv0)
    核心代码
    cpp
    void dfs(int u, int fa) {
    dpu0 = 0;
    dpu1 = valu;
    for (int v : gu) {
    if (v == fa) continue;
    dfs(v, u);
    dpu0 += max(dpv0, dpv1);
    dpu1 += dpv0;
    }
    }
    答案:
    cpp
    cout << max(dproot0, dproot1) << '\n';

多解法对比速查表

题目 推荐解法 备选解法 关键优化

1014 二进制拆分 DFS / 单调队列 可行性背包

1742 used 数组 bitset / 单调队列 多重部分和

1276 二进制拆分 单调队列 最大可达

3017 单调队列 + multiset 线段树 分段最大值

2373 单调队列 线段树 区间限制

1821 单调队列 线段树 工人刷墙

1159 LCS 滚动 区间 DP / 哈希 回文插入

3280 区间 DP 记忆化 插入删除取小

2955 区间 DP 栈 括号匹配

1141 区间 DP + 路径 记忆化 输出方案

1651 区间 DP 记忆化 矩阵连乘

1191 记忆化区间 DP 递推 棋盘分割

1160 区间 DP 四边形不等式 邮局

3254 状压 DP 轮廓线 相邻限制

1185 状压 DP 滚动数组 三行影响

2411 按行状压 轮廓线 DP 骨牌覆盖

2282 数位 DP 逐位统计 0 特殊

3252 数位 DP 组合计数 二进制

1088 记忆化搜索 排序 DP 最长下降

2342 树形 DP 拓扑排序 上司限制

相关推荐
顺顺 尼4 小时前
linux 进程信号(上)
linux·c++·算法
罗西的思考4 小时前
[Agent Memory / 强化学习] MemPO源码学习笔记 ---(5)--- GRPO
人工智能·算法·机器学习
richard_yuu4 小时前
动态规划:强化学习的「数学基础」,从 MDP 到值迭代
深度学习·神经网络·算法·yolo·机器学习·动态规划
mmmmath_34 小时前
LeetCode.225.用队列实现栈
算法
Elaine3364 小时前
数据结构与算法-程序
数据结构·python·算法·计算机基础·编程基础
2601_965742224 小时前
布局GEO AI本地营销,我重点看这几个细节
大数据·人工智能·算法·ai·新媒体运营
EatFan4 小时前
AI Agent 上生产前先加三道闸门:审批、限权、可回放的工程实践
人工智能·python·算法·多智能体·ai agent·mcp·harness
思茂信息5 小时前
CST软件BCI仿真模型及仿真案例
开发语言·单片机·嵌入式硬件·算法·emc
Persistent的粽子!5 小时前
双指针算法:最大盛水容器
c++·算法·leetcode