最长不下降子序列

最长不下降子序列

给定一个长度为 N 的整数序列:A1,A2,,AN

现在你有一次机会,将其中连续的 K 个数修改成任意一个相同值。

请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度。

最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数。

输入格式

输入第一行包含两个整数 NK

第二行包含 N 个整数 A1,A2,,AN

输出格式

输出一行包含一个整数表示答案。

数据范围

对于 20% 的评测用例,1KN100
对于 30% 的评测用例,1KN1000
对于 50% 的评测用例,1KN10000
对于所有评测用例,1KN1051Ai106

输入样例:

5 1
1 4 2 8 5

输出样例:

4

 

解题思路

  假设我们选取长度为k的区间[l,r]进行修改,然后在[1,l1]中先选择一个不下降子序列,假设以ai结尾,很明显为了使得不下降子序列最长,可以直接把i[l,r]内的数都修改成ai。然后再从j[r+1,n]中找到一个以aj开头的不下降子序列,其中要满足ajai,这样才能保证整个子序列是不下降的。

  可以发现当固定了要修改的区间后整个序列被分成了前后两个部分,而现在要从这两部分中选择相应的aiaj使得不下降子序列最长。由于我们需要用到以ai为结尾的最长不下降子序列,和以aj为开头的最长不下降子序列,因此要用线段树优化来求最长不下降子序列。

  定义f(i)表示所有以第i个数结尾的不下降子序列中长度的最大值,g(i)表示所有以第i个数开头的不下降子序列中长度的最大值。状态转移方程就是f(i)=max1j<iajai{f(j)}+1g(i)=maxi<jnajai{g(j)}+1

  当然还需要用到权值线段树去优化,这样才能做到O(nlogM)的时间复杂度,具体做法可以参考:线段树优化最长上升子序列问题

  考虑所有修改后的序列,我们可以把所有不下降子序列所构成的集合按照在前部分(假设修改区间为[l,r],前部分指[1,l1])中不下降子序列以哪个数结尾来进行划分。因此可以划分的类别有:没有前部分,即修改的区间是[1,k];以第1个数结尾;以第2个数结尾;一直到以第nk个数结尾,即修改的区间是[nk+1,n]

  对于以第i个数结尾的所有不下降子序列中,最大长度就是f(i)+k+maxr<jnajai{g(j)},其中r是修改区间的右端点,取值范围是r[i+k,n],这个修改区间可以是在i右部的任意一个长度为k的连续区间。首先ai固定了,因此前部分最长的不下降子序列就是f(i),然后再加上整个修改区间的长度,最后再加上在后部分满足开头至少为ai的最长的不下降子序列。

  对于没有前部分的情况,最大长度就是k+maxk<jn{g(i)}

  因此我们可从右往左枚举修改区间的右端点i,开一个权值线段树来维护右部分的g(i),这样就可以维护后缀最大值,查询右部分的最大值时直接在线段树中找到满足值大于等于aim中最大的g(i)

  AC代码如下,时间复杂度为O(nlogM)

复制代码
 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/

posted @   onlyblues  阅读(91)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-04-25 重新排列奶牛
Web Analytics
点击右上角即可分享
微信分享提示