最长不下降子序列
最长不下降子序列
给定一个长度为 的整数序列:。
现在你有一次机会,将其中连续的 个数修改成任意一个相同值。
请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度。
最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数。
输入格式
输入第一行包含两个整数 和 。
第二行包含 个整数 。
输出格式
输出一行包含一个整数表示答案。
数据范围
对于 的评测用例,;
对于 的评测用例,;
对于 的评测用例,;
对于所有评测用例,,。
输入样例:
5 1 1 4 2 8 5
输出样例:
4
解题思路
假设我们选取长度为的区间进行修改,然后在中先选择一个不下降子序列,假设以结尾,很明显为了使得不下降子序列最长,可以直接把内的数都修改成。然后再从中找到一个以开头的不下降子序列,其中要满足,这样才能保证整个子序列是不下降的。
可以发现当固定了要修改的区间后整个序列被分成了前后两个部分,而现在要从这两部分中选择相应的与使得不下降子序列最长。由于我们需要用到以为结尾的最长不下降子序列,和以为开头的最长不下降子序列,因此要用线段树优化来求最长不下降子序列。
定义表示所有以第个数结尾的不下降子序列中长度的最大值,表示所有以第个数开头的不下降子序列中长度的最大值。状态转移方程就是
当然还需要用到权值线段树去优化,这样才能做到的时间复杂度,具体做法可以参考:线段树优化最长上升子序列问题。
考虑所有修改后的序列,我们可以把所有不下降子序列所构成的集合按照在前部分(假设修改区间为,前部分指)中不下降子序列以哪个数结尾来进行划分。因此可以划分的类别有:没有前部分,即修改的区间是;以第个数结尾;以第个数结尾;一直到以第个数结尾,即修改的区间是。
对于以第个数结尾的所有不下降子序列中,最大长度就是,其中是修改区间的右端点,取值范围是,这个修改区间可以是在右部的任意一个长度为的连续区间。首先固定了,因此前部分最长的不下降子序列就是,然后再加上整个修改区间的长度,最后再加上在后部分满足开头至少为的最长的不下降子序列。
对于没有前部分的情况,最大长度就是。
因此我们可从右往左枚举修改区间的右端点,开一个权值线段树来维护右部分的,这样就可以维护后缀最大值,查询右部分的最大值时直接在线段树中找到满足值大于等于中最大的。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10, M = 1e6 + 10; 5 6 int a[N]; 7 int f[N], g[N]; 8 struct Node { 9 int l, r, maxv; 10 }tr[M * 4]; 11 12 void build(int u, int l, int r) { 13 if (l == r) { 14 tr[u] = {l, r, 0}; 15 } 16 else { 17 int mid = l + r >> 1; 18 build(u << 1, l, mid); 19 build(u << 1 | 1, mid + 1, r); 20 tr[u] = {l, r, 0}; 21 } 22 } 23 24 void modify(int u, int x, int c) { 25 if (tr[u].l == tr[u].r) { 26 tr[u].maxv = c; 27 } 28 else { 29 if (x <= tr[u].l + tr[u].r >> 1) modify(u << 1, x, c); 30 else modify(u << 1 | 1, x, c); 31 tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv); 32 } 33 } 34 35 int query(int u, int l, int r) { 36 if (tr[u].l >= l && tr[u].r <= r) return tr[u].maxv; 37 int mid = tr[u].l + tr[u].r >> 1, ret = 0; 38 if (l <= mid) ret = query(u << 1, l, r); 39 if (r >= mid + 1) ret = max(ret, query(u << 1 | 1, l, r)); 40 return ret; 41 } 42 43 int main() { 44 int n, m; 45 scanf("%d %d", &n, &m); 46 for (int i = 1; i <= n; i++) { 47 scanf("%d", a + i); 48 } 49 build(1, 1, M - 1); 50 for (int i = 1; i <= n; i++) { // 求以a[i]结尾的最长不下降子序列 51 f[i] = query(1, 1, a[i]) + 1; 52 modify(1, a[i], f[i]); 53 } 54 build(1, 1, M - 1); 55 for (int i = n; i; i--) { // 求以a[i]开头的最长不下降子序列 56 g[i] = query(1, a[i], M - 1) + 1; 57 modify(1, a[i], g[i]); 58 } 59 build(1, 1, M - 1); 60 int ret = 0; 61 for (int i = n; i >= m; i--) { 62 // 边界是i=m,此时修改的区间是[1~m],对应不存在前部分的情况。而此时f[0]=a[0]=0,因此用同样的方法得到的结果也是正确的 63 ret = max(ret, f[i - m] + m + query(1, a[i - m], M - 1)); 64 modify(1, a[i], g[i]); 65 } 66 printf("%d", ret); 67 68 return 0; 69 }
参考资料
AcWing 4648. 最长不下降子序列:https://www.acwing.com/solution/content/143797/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17353338.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-04-25 重新排列奶牛