纪念品

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

把最小删除转化为最长保留:类 LIS 的 O(n²) 线性 DP,f[i] = 1 + max{f[j] : |a[i]-a[j]|≠1},答案 N - max f。

OJ: roj

题目 ID: 8015

难度:普及-

标签:线性dpLIS子序列

创建: 2026-10-02 17:00

更新: 2026-10-06 16:51

题目描述

小 K 买回 NN 个纪念品,按购买顺序排成一排,每个纪念品有一个价格。她可以去掉一些,使剩下的纪念品中任意相邻两个的价格差的绝对值不等于 1(价格相同可以相邻),求最少去掉多少个。输入第一行一个整数 NN,接下来 NN 行每行一个价格;输出最少去掉的个数。数据范围 3≤N≤333 \le N \le 33。样例输入为 6 及价格 4 2 2 1 1 1,输出 2。

思路

删得最少等价于留得最多,于是求最长的合法子序列。合法性只依赖相邻两个保留元素,仿照 LIS 设 f[i]f[i] 表示一定保留第 ii 个纪念品时最多能保留多少个,转移为 f[i]=1+max⁡{f[j]:j<i, ∣ai−aj∣≠1}f[i] = 1 + \max\{f[j] : j < i,\ |a_i - a_j| \neq 1\},答案为 N−max⁡f[i]N - \max f[i],复杂度 O(N2)O(N^2)。注意差为 0 是允许的,判断条件是「不等于 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-10-02 17:00
# update_at: 2026-10-02 17:00

import sys


def best_keep(prices: list[int]) -> int:
    """最多能保留多少个纪念品:以每个下标结尾的最长合法子序列(类 LIS)。"""
    keep = [1] * len(prices)  # keep[i]:一定保留第 i 个纪念品时,最多保留多少个
    for i, price in enumerate(prices):
        # 保留的第 j 个在 i 前面,且它接到 i 身边时差值不为 1,才能转移
        keep[i] = 1 + max(
            (keep[j] for j, prev in enumerate(prices[:i]) if abs(price - prev) != 1),
            default=0,  # 前面一个都接不上时,只保留 i 自己
        )
    return max(keep)


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    prices = [next(data) for _ in range(n)]
    print(n - best_keep(prices))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

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-06 16:51
 * update_at: 2026-10-06 16:51
 */

#include <iostream>
#include <cstdlib>
using namespace std;

typedef long long ll;

const int MAXN = 1005;

ll price[MAXN]; // price[i] 表示第 i 个纪念品的价格
ll f[MAXN];     // f[i] 表示一定保留第 i 个纪念品时最多能保留多少个

int main() {
    ll n;
    cin >> n;
    for (ll i = 1; i <= n; i++) {
        cin >> price[i];
    }

    ll best_keep = 0;
    for (ll i = 1; i <= n; i++) {
        f[i] = 1; // 前面接不上任何纪念品时,只保留第 i 个
        for (ll j = 1; j < i; j++) {
            // 保留序列里 i 的前一个元素是 j,两者价格差的绝对值不能为 1
            if (abs(price[i] - price[j]) != 1 && f[j] + 1 > f[i]) {
                f[i] = f[j] + 1;
            }
        }
        if (f[i] > best_keep) {
            best_keep = f[i];
        }
    }

    // 删得最少 = 留得最多,答案为总数减去最长合法子序列长度
    cout << n - best_keep << endl;
    return 0;
}