题解:学而思编程 递增
【题目来源】
学而思编程:递增
【题目描述】
现有数列 \(A_1,A_2,⋯ ,A_N\) 修改最少的数字(可以修改成小数),使得数列严格单调递增。
【输入】
第 \(1\) 行,\(1\) 个整数 \(N\)
第 \(2\) 行,\(N\) 个整数 \(A_1,A_2,⋯ ,A_N\)
【输出】
\(1\) 个整数,表示最小修改的数字个数
【输入样例】
3
1 3 2
【输出样例】
1
【核心思想】
-
问题分析:给定长度为 \(N\) 的数列,要求修改最少的数字使得数列严格单调递增。等价于求最长上升子序列(LIS),因为保留的最多不修改的元素就是 LIS,需要修改的元素个数为 \(N - \text{LIS长度}\)。
-
算法选择:
- 贪心 + 二分优化:维护数组 \(b\),\(b[i]\) 表示长度为 \(i\) 的上升子序列的最小结尾元素
- lower_bound:使用二分查找找到第一个大于等于 \(a[i]\) 的位置
-
关键步骤:
- 初始化:\(len = 0\),\(b[0]\) 为极小值
- 对于每个元素 \(a[i]\):
- 若 \(a[i] > b[len]\),则 \(b[++len] = a[i]\),扩展 LIS
- 否则,用
lower_bound找到位置 \(m\),更新 \(b[m] = a[i]\),保持最小性
- 答案:\(N - len\),总元素数减去 LIS 长度
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),每个元素二分查找
- 空间复杂度:\(O(N)\),数组 \(b\) 和 \(f\)
-
LIS 贪心优化的核心思想:
- 转化思想:最少修改次数 = 总元素数 - 最长不修改的元素个数(LIS)
- 贪心策略:同样长度的子序列,结尾越小越有利于后续扩展
- 二分查找:利用 \(b\) 数组的单调性,用
lower_bound快速定位 - 替换策略:用更小的值替换,保持 \(b\) 数组的最小性
- 适用于最少修改/删除使序列递增的问题
【算法标签】
线性DP-一维
【代码详解】
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100005;
int n, a[N], b[N], f[N], maxn, len; // b: 最长递增序列, f: 每个元素所在的LIS长度
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i]; // 输入序列
}
for (int i = 1; i <= n; i++)
{
if (a[i] > b[len]) // 当前元素大于序列最后一个元素
{
b[++len] = a[i]; // 扩展序列
f[i] = len; // 记录当前元素所在的LIS长度
}
else
{
// 找到第一个大于等于a[i]的位置
int m = lower_bound(b + 1, b + len + 1, a[i]) - b;
b[m] = a[i]; // 替换该位置的元素
f[i] = m; // 记录当前元素所在的LIS长度
}
}
cout << n - len; // 输出最少需要删除的元素个数
return 0;
}
【运行结果】
3
1 3 2
1
浙公网安备 33010602011771号