链接
题意
一个数列n个数,m个询问。问一段区间内所有子区间的 f(x)= max(abs(h[i] - h[j]) / (i - j)) 之和为多少
思路
首先把每个点在坐标系中画出来,可以发现所有的f(x)一定是相邻两个数产生的。现在对相邻的求差就形成了一个新数列。 对于新数列中的每个数,用ST求他两侧最近的且比他大的是哪个位置,那么这个值就是在这一段区间内取他时的所有子区间的f(x)值。 这里要注意的就是一侧包括等于号另一侧不包括,不然会导致重复计算。第一个样例的最后一个询问就是这种情况。
代码
#include <bits/stdc++.h>
#define LL long long
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
int f[MAXN][30];
int a[MAXN];
int Left[MAXN], Right[MAXN];
inline int get_max(int left, int right){
int d = log(double(right - left + 1)) / log(2.0);
return max(f[left][d], f[right - (1 << d) + 1][d]);
}
inline LL calc(LL n){
if(n < 2){
return 1;
}
return n * (n - 1) / 2;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif // ONLINE_JUDGE
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
}
for(int i = 1; i < n; ++i){
f[i][0] = abs(a[i + 1] - a[i]);
// printf("%d ", f[i][0]);
}
// printf("\n");
--n;
for(int i = 1; i <= log((double)n) / log(2.0); ++i){
for(int j = 1; j <= n - (1 << i) + 1; ++j){
f[j][i] = max(f[j][i - 1], f[j + (1 << (i - 1))][i - 1]);
}
}
int left, right, mid, res;
for(int i = 1; i <= n; ++i){
//找往左第一个比b[i]大
left = 1; right = i - 1;
while(left < right){
mid = (right + left) >> 1;
res = get_max(mid + 1, right);
if(res > f[i][0]){
left = mid + 1;
}
else{
right = mid;
}
}
if(f[left][0] > f[i][0]){
Left[i] = left;
}
else{
Left[i] = -1;
}
//找往右第一个比b[i]大
left = i + 1, right = n;
while(left < right){
mid = (right + left) >> 1;
res = get_max(left, mid);
if(res >= f[i][0]){
right = mid;
}
else{
left = mid + 1;
}
}
if(f[left][0] >= f[i][0]){
Right[i] = left;
}
else{
Right[i] = INF;
}
}
LL ans;
int x, y;
for(int i = 1; i <= m; ++i){
scanf("%d%d", &x, &y);
--y;
ans = 0;
for(int j = x; j <= y; ++j){
LL left = max(x, Left[j] + 1);
LL right = min(y, Right[j] - 1);
ans += (j - left + 1) * (right - j + 1) * (LL)f[j][0];
}
printf("%I64d\n", ans);
}
}