【例9.23】橱窗布置(flower)

GitHub跳转原题关系图返回列表

把“上一束花落在哪个花瓶”压成前缀最大值 g[i][t],用 O(FV) 线性 DP 求最大美学值,再逐层取最靠左的最优落点回溯出严格递增的摆放方案。

OJ: roj

题目 ID: 1279

难度:普及-

标签:普及-动态规划线性DP前缀最大值回溯python

创建: 2026-09-30 03:02

更新: 2026-10-05 07:51

题目描述

橱窗里从左到右摆着编号 11 到 VV 的花瓶,要放入 FF 束花,花束 ii 放进花瓶 jj 的美学值为 AijA_{ij}(空花瓶美学值为 00);必须保持花束编号的顺序(i<ji<j 时花束 ii 要放在花束 jj 左边的花瓶里),求总美学值最大的摆放方式,最优方案不唯一时输出任意一种。输入第一行为 F,VF, V,随后 FF 行每行 VV 个整数给出 AijA_{ij};输出第一行为最大美学值,第二行为 FF 个整数,第 KK 个数表示花束 KK 所在的花瓶编号。数据范围:1⩽F⩽1001\leqslant F\leqslant 100,F⩽V⩽100F\leqslant V\leqslant 100,−50⩽Aij⩽50-50\leqslant A_{ij}\leqslant 50。样例:输入依次为 3 5 / 7 23 -5 -24 16 / 5 21 -4 10 23 / -21 5 -4 -20 20,输出依次为 53 / 2 4 5。

思路

设 g[i][t]g[i][t] 为花束 1..i1..i 依次放进编号不超过 tt 的花瓶能取得的最大美学值,转移 g[i][t]=max⁡(g[i][t−1], g[i−1][t−1]+Ai,t)g[i][t]=\max(g[i][t-1],\ g[i-1][t-1]+A_{i,t}):花瓶 tt 要么空着,要么插第 ii 束花,t<it<i 时为 −∞-\infty,答案即 g[F][V]g[F][V],总复杂度 O(FV)O(FV)。还原方案时从右往左逐层处理:利用 g[i]g[i] 沿 tt 单调不减的性质,每层取最靠左、又取到该层目标值的位置——由最小性可知该位置必来自“插花”那一支,于是第 ii 束花落定、目标值换成 g[i−1][pos−1]g[i-1][pos-1],得到的编号天然严格递增。

参考代码

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;
}