题解 - Codeforces 1468A LaIS

另一个好想好写的做法,看题解区还没有就来一发。

简述题意

给定数列 a1,,ana_1,\ldots,a_n,要求找到最长的子序列 b1,,bm(m2)b_1,\ldots,b_m(m\ge 2) 满足 1km2,min(bk,bk+1)min(bk+1,bk+2)\forall 1\le k\le m-2,\min(b_k,b_{k+1})\le \min(b_{k+1},b_{k+2})1ain1\le a_i\le nn5×105\sum n\le 5\times 10^5

分析

经典思路是考虑动态规划,令 fif_i 为以第 ii 位为结尾的最大合法子序列长度。

假设 ii 的前驱是 jj

  • ajaia_j\le a_i 则总是满足条件,直接用 fj+1f_j+1fif_i 贡献,从左往右扫的时候实时维护前缀 max\max
  • aj>aia_j > a_i:令 jj 的前驱为 kk,则 min(ak,aj)min(aj,ai)\min(a_k, a_j)\le \min(a_j, a_i),即 akaia_k\le a_i。那就让 jj 越靠右越优。用单调栈找到每个 ii 对应的最靠右的 jj 满足 aj>aia_j>a_i,在 jj 处查询前缀 max\max 即可。

这里的前缀 max\max 就是:维护 gig_i 为以 ii 的值为结尾的最大 ff 值,这里的转移就是 gg 数组的前缀求 max\max。不减地单点修改,求前缀 max\max,可以用树状数组实现。时间复杂度 O(nlogn)O(n\log n),空间复杂度 O(n)O(n)

代码

实现时重复计入了 aj=aia_j=a_i 的贡献,显然没关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
using namespace std;

const int MXN = 500005;
int T, N, arr[MXN], stk[MXN], top, val1[MXN], mxv[MXN], ans;
vector<int> task[MXN];

int query(int p) {
int v(0);
while (p) v = max(v, mxv[p]), p -= p & -p;
return v;
}
void setv(int p, int v) {
while (p <= N) mxv[p] = max(mxv[p], v), p += p & -p;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> N;
top = -1;
for (int i(0); i != N; ++i) {
cin >> arr[i];
task[i].clear();
val1[i] = 0;
while (~top && arr[stk[top]] < arr[i]) --top;
if (~top) task[stk[top]].push_back(i);
stk[++top] = i;
}
for (int i(1); i <= N; ++i) mxv[i] = 0;
ans = 0;
for (int i(0); i != N; ++i) {
ans = max(ans, val1[i] = max(val1[i], query(arr[i]) + 1));
for (int j : task[i]) {
val1[j] = max(val1[j], query(arr[j]) + 2);
}
setv(arr[i], val1[i]);
}
cout << ans << endl;
}
return 0;
}

题解 - Codeforces 1468A LaIS
http://sunsetglow95.github.io/2024/09/11/sol-CF1468A/
作者
SunsetGlow95
发布于
2024年9月11日
许可协议