线性 DP + 方案还原: dp[i][j] 表示前 i 束花放入前 j 个花瓶的最大美学值, 按放或不放转移, 再反向还原出递增方案.
OJ: luogu
题目 ID: P1854
难度:普及
标签:动态规划线性DP方案还原c++python
创建: 2026-10-26 14:01
更新: 2026-10-26 16:36
形式化题目
给定
正解
思路
花束按编号顺序摆放, 等价于在矩阵里从左到右选一条递增下标序列. 只看前
- 不放: 问题变成前
束花放入前 个花瓶, 值 dp[i][j-1]. - 放: 花束
占花瓶 , 前面 束花只能放前 个花瓶, 值 dp[i-1][j-1] + a[i][j].
于是 dp[i][j] = max(dp[i][j-1], dp[i-1][j-1] + a[i][j]). 初始 dp[0][j] = 0, 其余负无穷, 答案 dp[F][V].
下面用样例 dp[i][j] 的变化:
| i \ j | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | -inf | 7 | 23 | 23 | 23 | 23 |
| 2 | -inf | -inf | 28 | 28 | 33 | 46 |
| 3 | -inf | -inf | -inf | 24 | 24 | 53 |
表前看三点: dp[0][j] 恒为 0, j < i 的格子保持负无穷(不可达), 每一行从左到右不降. 表后看: dp[3][5] = 53 是答案; 第一行从左到右的最大值 23 说明花束 1 最优放花瓶 2.
方案还原从 (F, V) 往回走: 若 dp[i][j] == dp[i][j-1], 说明最优解没用到花瓶
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-26 13:36
* update_at: 2026-10-26 16:36
*/
// main.cpp: 正式主解. dp[i][j] = 前 i 束花放入前 j 个花瓶的最大美学值.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 105; // F, V 都不超过 100
const ll NEG = -(1LL << 60); // 负无穷哨兵: 美学值最小为 -50*100, 远大于 NEG
ll F, V; // F 束花, V 个花瓶
ll a[MAXN][MAXN]; // a[i][j]: 花束 i 放入花瓶 j 的美学值
ll dp[MAXN][MAXN]; // dp[i][j]: 前 i 束花放入前 j 个花瓶的最大美学值
ll path[MAXN]; // path[t]: 还原方案时从后往前记录的花瓶编号
void read_data() {
cin >> F >> V;
for (int i = 1; i <= F; i++) {
for (int j = 1; j <= V; j++) {
cin >> a[i][j];
}
}
}
void solve() {
// 初始化: 0 束花放入任意多个花瓶美学值为 0; 其余状态置为负无穷.
for (int i = 0; i <= F; i++) {
for (int j = 0; j <= V; j++) {
dp[i][j] = NEG;
}
}
for (int j = 0; j <= V; j++) {
dp[0][j] = 0;
}
// 转移: 花束 i 不放花瓶 j -> dp[i][j-1]; 放 -> dp[i-1][j-1] + a[i][j].
for (int i = 1; i <= F; i++) {
for (int j = i; j <= V; j++) {
ll not_use = dp[i][j - 1];
ll use = dp[i - 1][j - 1] + a[i][j];
dp[i][j] = max(not_use, use);
}
}
cout << dp[F][V] << "\n";
// 方案还原: 从 (F, V) 反推. 若 dp[i][j] == dp[i][j-1], 说明最优解没用花瓶 j,
// 左移; 否则花束 i 一定放在花瓶 j, 记录后向左上角走一步.
ll i = F;
ll j = V;
ll cnt = 0;
while (i > 0) {
if (j > i && dp[i][j] == dp[i][j - 1]) {
j--;
} else {
path[++cnt] = j;
i--;
j--;
}
}
// path 是逆序记录的, 倒序输出得到递增序列.
for (int k = cnt; k >= 1; k--) {
cout << path[k] << (k == 1 ? '\n' : ' ');
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_data();
solve();
return 0;
}py
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-26 13:36
# update_at: 2026-10-26 13:36
import sys
NEG = -10**18 # 负无穷哨兵: 表示 dp 状态不可达
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
f = next(data) # 花束数
v = next(data) # 花瓶数
# a[i][j]: 花束 i 放入花瓶 j 的美学值, 下标从 1 开始.
a = [[0] * (v + 1)] + [[0] + [next(data) for _ in range(v)] for _ in range(f)]
# dp[i][j]: 前 i 束花放入前 j 个花瓶(每瓶至多一束)的最大美学值.
dp = [[NEG] * (v + 1) for _ in range(f + 1)]
dp[0] = [0] * (v + 1)
for i in range(1, f + 1):
for j in range(i, v + 1):
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j - 1] + a[i][j])
# 从 (f, v) 反推方案: 不取花瓶 j 就左移, 否则花束 i 放花瓶 j, 向左上走.
plan: list[int] = []
i, j = f, v
while i > 0:
if j > i and dp[i][j] == dp[i][j - 1]:
j -= 1
else:
plan.append(j)
i -= 1
j -= 1
print(dp[f][v])
print(" ".join(map(str, plan[::-1])))
if __name__ == "__main__":
solve()复杂度
- 时间复杂度:
, 填表 个状态, 每个状态 , 方案还原 . - 空间复杂度:
.
总结
本题把"花束顺序对应花瓶编号递增"的约束直接写进 DP 状态, 用"放或不放当前花瓶"的二选一转移消除组合枚举. 方案不唯一, 还原时只要始终沿真实转移反推, 就能得到一组合法递增方案.