CF689D Friends and Subsequences

考点

  • 线段树
  • 二分

题解

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 50;
int n, a[maxn], b[maxn];

// 0:代表mx
// 1:代表mi
int mx[4 * maxn], mi[4 * maxn];

void build_mx(int p, int l, int r) {
if (l == r) {
mx[p] = a[l];
return;
}
int mid = (l + r) >> 1;
build_mx(p << 1, l, mid), build_mx((p << 1) | 1, mid + 1, r);
mx[p] = max(mx[p << 1], mx[(p << 1) | 1]);
}

void build_mi(int p, int l, int r) {
if (l == r) {
mi[p] = b[l];
return;
}
int mid = (l + r) >> 1;
build_mi(p << 1, l, mid), build_mi((p << 1) | 1, mid + 1, r);
mi[p] = min(mi[p << 1], mi[(p << 1) | 1]);
}

inline int ask_mx(int p, int l, int r, int L, int R) {
if (L <= l && r <= R) return mx[p];
int mid = (l + r) >> 1;
if (R <= mid)
return ask_mx(p << 1, l, mid, L, R);
else if (L > mid)
return ask_mx((p << 1) | 1, mid + 1, r, L, R);
else
return max(ask_mx(p << 1, l, mid, L, R),
ask_mx((p << 1) | 1, mid + 1, r, L, R));
}

inline int ask_mi(int p, int l, int r, int L, int R) {
if (L <= l && r <= R) return mi[p];
int mid = (l + r) >> 1;
if (R <= mid)
return ask_mi(p << 1, l, mid, L, R);
else if (L > mid)
return ask_mi((p << 1) | 1, mid + 1, r, L, R);
else
return min(ask_mi(p << 1, l, mid, L, R),
ask_mi((p << 1) | 1, mid + 1, r, L, R));
}

int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) cin >> b[i];
build_mx(1, 1, n), build_mi(1, 1, n);
ll ans = 0;
int mid, mx, mi, ans_l, ans_r;
for (int i = 1; i <= n; ++i) {
// 找左边界
int l = i, r = n;
while (l <= r) {
mid = (l + r) >> 1;
mx = ask_mx(1, 1, n, i, mid), mi = ask_mi(1, 1, n, i, mid);
if (mx - mi >= 0)
r = mid - 1;
else
l = mid + 1;
}
ans_l = l;
// 找右边界
l = i, r = n;
while (l <= r) {
mid = (l + r) >> 1;
mx = ask_mx(1, 1, n, i, mid), mi = ask_mi(1, 1, n, i, mid);
if (mx - mi <= 0)
l = mid + 1;
else
r = mid - 1;
}
ans_r = r;
ans += ans_r - ans_l + 1;
}
cout << ans << endl;
return 0;
}

思路

使用线段树的时间复杂度是\(n\log ^2n\),需要非常卡常才能过,建议使用ST表进行优化,毕竟只需要求区间极值而不需要动态修改。

固定左端点l,移动右端点r,可以发现一个数学规律: \[ \mathop {\small{\overset{r+1}{\max}}} \limits_{i=l}\,\,a_i\,\,\geqslant \,\,\mathop {\small{\overset{r}{\max}}} \limits_{i=l}\,\,a_i \]

\[ \mathop {\small{\overset{r+1}{\min}}} \limits_{i=l}\,\,b_i\,\,\leqslant \,\,\mathop {\small{\overset{r}{\min}}} \limits_{i=l}\,\,b_i \]

两者结合,变成: \[ \mathop {\small{\overset{r+1}{\max}}} \limits_{i=l}\,\,a_i-\mathop {\small{\overset{r+1}{\min}}} \limits_{i=l}\,\,b_i\,\,\geqslant \mathop {\small{\overset{r}{\max}}} \limits_{i=l}\,\,a_i-\mathop {\small{\overset{r}{\min}}} \limits_{i=l}\,\,b_i \] 抽象一下,变成: \[ f\left( i+1, j+1 \right) -f\left( i, j \right) \geqslant 0 \] 也就是说,固定左端点l,右端点r向右移动时,f函数的值是单调不减的。

现在的目标,是找到能让函数f的值为0的区间有多少个。

由于存在单调性,外循环每一次固定一个l,内循环对右端点r进行二分,找到等于0的左右边界即可。

0区间的左边界:

如果f >= 0,就向左边界移动,否则向右边界,得到左边界L

0区间的右边界:

如果f <= 0,就向右边界移动,否则向左边界,得到右边界R

那么本轮外循环对答案的贡献就是R - L + 1,开启下一轮外循环