Problem
Solution
自从比赛我打错了这题的60分暴力后,我幼小的内心中留下了一道阴影。
多组数据一定要记得清空全局变量和数组啊啊啊!!!
甚至为了吸取教训,我还准备专门写一篇。。
这题的60分做法就是固定左端点,随着右端点的右移更新最值并贡献答案。考虑100分的做法,当时良久都不知道怎么做,都在想有什么数据结构能大显神通,结果比赛后看了题解,发现做法很玄学。
由于排列是随机的,暴力解法中,固定左端点后,更新最大最小值成功的次数大约是logn级别的(别问我问什么),于是我们只需要预处理出,最大值或最小值变化的那个点,然后中间的那些可以一起算。
我们从右往左左,考虑如何快速求出第一个比i大(小)的j。我们可以利用各种数据结构,线段树,平衡树之类的,但是最简洁强大的还是单调栈。我们以求第一个比i大的位置为例,我们维护一个单调下降的的栈(从底到顶),如果在栈顶小于等于当前值就将其弹出,记录栈顶即可。插入则直接插入栈顶。保证栈顶的元素一定是最小的那个。这样假设a[i+1]>a[i],i+1就是答案,否则答案也必然在栈中(只用想想弹出的前提是什么)。另一个栈也差不多。
这样我们在枚举R的时候,同时记录一下[L,R]的最大值和最小值,然后看看哪个先变化就让R跳过去,同时计算答案、更新当前最值就可以了。而这样的时间复杂度是 O(nlogn) 的。
Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define maxn 100010
using namespace std;
typedef long long LL;
int T;
int n, a[maxn];
int st1[maxn], st2[maxn], top1, top2;
int nxtMax[maxn], nxtMin[maxn];
LL sum, ans[maxn];
int main(){
freopen("sum.in", "r", stdin);
freopen("sum.out", "w", stdout);
scanf("%d", &T);
while(T --){
scanf("%d", &n);
top1 = top2 = 0;
memset(ans, 0, sizeof(ans));
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
st1[0] = st2[0] = n + 1;
for(int i = n; i > 0; i--){
while(top1 && a[st1[top1]] <= a[i]) top1 --;
while(top2 && a[st2[top2]] >= a[i]) top2 --;
nxtMax[i] = st1[top1];
nxtMin[i] = st2[top2];
st1[++top1] = i;
st2[++top2] = i;
}
for(int L = 1; L <= n; L++){
for(int R = L, mx = L, mi = L, ne; R <= n; R = ne){
if(nxtMax[mx] < nxtMin[mi]){
ne = nxtMax[mx];
ans[a[mx] - a[mi]] += ne - R;
mx = ne;
}
else{
ne = nxtMin[mi];
ans[a[mx] - a[mi]] += ne - R;
mi = ne;
}
}
}
sum = 0LL;
for(int i = 0; i < n; i++){
sum += ans[i];
printf("%lld\n", sum);
}
}
return 0;
}