把“上一束花落在哪个花瓶”压成前缀最大值 g[i][t],用 O(FV) 线性 DP 求最大美学值,再逐层取最靠左的最优落点回溯出严格递增的摆放方案。
OJ: roj
题目 ID: 1279
难度:普及-
标签:普及-动态规划线性DP前缀最大值回溯python
创建: 2026-09-30 03:02
更新: 2026-10-05 07:51
题目描述
橱窗里从左到右摆着编号 3 5 / 7 23 -5 -24 16 / 5 21 -4 10 23 / -21 5 -4 -20 20,输出依次为 53 / 2 4 5。
思路
设
参考代码
Python 版:
python
#!/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-09-30 03:03
# update_at: 2026-09-30 03:13
import sys
from bisect import bisect_left
NEG = -10**9 # 美学值总和最小也只有 -50*100,用它表示“这些花束装不下”
def prefix_levels(v: int, a: list[list[int]]) -> list[list[int]]:
"""levels[i][t]:花束 1..i 依次放进编号不超过 t 的花瓶时的最大美学值。
只存前缀最大值即可:第 i 束花定在花瓶 t 时,前 i-1 束花只要全部挤在 t 左侧,
具体占哪几个花瓶不影响后面的选择,于是“最后一束花放在哪”这一维被压掉了。
"""
levels = [[0] * (v + 1)] # 第 0 层:没有花束,任何花瓶前缀的美学值都是 0
for i, row in enumerate(a, 1):
prev, cur = levels[-1], [NEG] * (v + 1)
for t in range(i, v + 1): # 不足 i 个花瓶放不下 i 束花,保持 NEG
cur[t] = max(cur[t - 1], prev[t - 1] + row[t - 1]) # 花瓶 t 空着 / 插第 i 束花
levels.append(cur)
return levels
def used_vases(f: int, v: int, levels: list[list[int]]) -> list[int]:
"""从右往左回溯:每束花都取“最靠左、又能拼出该层最优值”的花瓶。"""
pick, target, limit = [0] * f, levels[f][v], v + 1
for i in range(f, 0, -1):
pos = bisect_left(levels[i], target, i, limit) # 该层前缀值第一次达到 target 的花瓶
pick[i - 1] = pos
if i > 1:
# 第 i 束花占了 pos,前 i-1 束花的目标值就是它们在前 pos-1 个花瓶里的最优值
target, limit = levels[i - 1][pos - 1], pos
return pick
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
f, v = next(data), next(data)
a = [[next(data) for _ in range(v)] for _ in range(f)] # a[i][j]:花束 i+1 放花瓶 j+1 的美学值
levels = prefix_levels(v, a)
print(levels[f][v])
print(*used_vases(f, v, levels))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
// main.cpp:O(FV) 线性 DP 求最大美学值,再逐层回溯输出方案。
/**
* 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-05 07:51
* update_at: 2026-10-05 07:51
*/
#include <cstdio>
typedef long long ll;
const int MAXN = 105;
const ll NEG = -1e9; // 表示“用前 t 个花瓶放不下 i 束花”
int f, v;
int a[MAXN][MAXN]; // a[i][j]:花束 i 放进花瓶 j 的美学值
ll g[MAXN][MAXN]; // g[i][t]:花束 1..i 依次放进编号不超过 t 的花瓶的最大美学值
int pick[MAXN]; // pick[i]:第 i 束花选的花瓶编号
// 填前缀最大值表:花瓶 t 要么空着(g[i][t-1]),要么插第 i 束花(g[i-1][t-1]+a[i][t])
void solve() {
for (int t = 0; t <= v; ++t)
g[0][t] = 0; // 没有花束时美学值为 0
for (int i = 1; i <= f; ++i) {
g[i][i - 1] = NEG; // 不足 i 个花瓶放不下 i 束花
for (int t = i; t <= v; ++t) {
ll best = g[i][t - 1]; // 花瓶 t 空着
if (g[i - 1][t - 1] + a[i][t] > best)
best = g[i - 1][t - 1] + a[i][t]; // 花瓶 t 插第 i 束花
g[i][t] = best;
}
}
// 从右往左回溯:g[i] 逐层单调不减,取最靠左、又取到该层目标值的花瓶,
// 这个位置一定来自“插花”那一支,编号天然严格递增;
// t < i 时 g[i][t] = NEG,不会误配,循环必然停在 [i, v] 内
ll target = g[f][v];
for (int i = f; i >= 1; --i) {
int pos = i; // 第一个取到 target 的位置,必有 g[i][pos] == target
while (g[i][pos] < target)
++pos;
pick[i] = pos;
target = g[i - 1][pos - 1]; // 前 i-1 束花的目标值
}
printf("%lld\n", g[f][v]);
for (int i = 1; i <= f; ++i)
printf("%d%c", pick[i], i == f ? '\n' : ' ');
}
int main() {
scanf("%d %d", &f, &v);
for (int i = 1; i <= f; ++i)
for (int j = 1; j <= v; ++j)
scanf("%d", &a[i][j]);
solve();
return 0;
}