把最小删除转化为最长保留:类 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 买回 6 及价格 4 2 2 1 1 1,输出 2。
思路
删得最少等价于留得最多,于是求最长的合法子序列。合法性只依赖相邻两个保留元素,仿照 LIS 设
参考代码
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;
}