模拟赛-sequence(最大最小值分治)

题目描述

题目描述:

给出一个 n 个数的序列,一个区间的价值是区间内最小值乘最大值的 积。求所有区间价值和。答案对 998244353 取模。

样例输入:

5 4 2 3 1 5

样例输出:

107

数据范围:

$n\le 5*10^5,a_i\le 10^9$

Solution

我们考虑分治,然后这时候我们注意到左边的后缀和右边的前缀的 $max$ 以及 $min$ 都是单调的

然后我们枚举左端点,然后找到最靠右的右边的前缀 $max$ 小于等于左边区间的 $max$ 的位置 $pm$

同理找到最靠右的右边的前缀 $min$ 大于等于左边区间的 $min$ 的位置 $pn$

那么对于右端点在 $[m+1,min\lbrace pn,pm\rbrace]$,区间最大和最小值都是在左边,可以直接计算答案

右端点在 $[min\lbrace pn,pm\rbrace+1,max\lbrace pn,pm\rbrace]$,区间最大或者最小在左边,另一个在右边,可以预处理得到答案

右端点在 $[\lbrace pn,pm\rbrace+1,r]$,最大和最小值都在右边,可以预处理得到答案

时间复杂度 $O(n\log n)$

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
#include <iostream>
#include <cstdio>
#define maxn 500010
#define ll long long
#define INF 1000000000
using namespace std;

const int p = 998244353;

int n, a[maxn];

ll ans;

int mn[maxn], mx[maxn]; ll v[maxn], vn[maxn], vm[maxn];
void solve(int l, int r) {
if (l == r) return (void) (ans = (ans + (ll) a[l] * a[l]) % p);
int m = l + r >> 1, pn = m, pm = m; ll Min = INF, Max = -INF;
mn[m] = INF; mx[m] = -INF; v[m] = vn[m] = vm[m] = 0;
for (int i = m + 1; i <= r; ++i) {
mn[i] = min(mn[i - 1], a[i]);
mx[i] = max(mx[i - 1], a[i]);
v[i] = (v[i - 1] + (ll) mx[i] * mn[i]) % p;
vn[i] = (vn[i - 1] + mn[i]) % p;
vm[i] = (vm[i - 1] + mx[i]) % p;
}
for (int i = m; i >= l; --i) {
Min = min(Min, (ll) a[i]);
Max = max(Max, (ll) a[i]);
while (pn < r && mn[pn + 1] >= Min) ++pn;
while (pm < r && mx[pm + 1] <= Max) ++pm;
ans = (ans + Min * Max % p * (min(pn, pm) - m)) % p;
if (pn < pm)
ans = (ans + (vn[pm] - vn[pn]) * Max) % p;
else if (pm < pn)
ans = (ans + (vm[pn] - vm[pm]) * Min) % p;
ans = (ans + v[r] - v[max(pn, pm)]) % p;
}
solve(l, m); solve(m + 1, r);
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
solve(1, n); cout << (ans + p) % p << "\n";
return 0;
}