线段树加一点剪枝, 固定每个点为左端点,找出做最大近似区间,然后在这个区间中找有多少个满足题意的右端点,线段树查询最大值。倒着扫描。
代码
#include <bits/stdc++.h>
using namespace std;
#define rs rt << 1|1
#define ls rt << 1
const int N = 50000 + 5;
int tree[N << 2];
int a[N];
void build(int l, int r, int rt){
if(l == r){
tree[rt] = a[l];
return ;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r ,rs);
tree[rt] = max(tree[ls], tree[rs]);
}
int query(int l ,int r ,int rt, int L, int R , int val){
if(l == r){
if(a[l] >= val){
return l;
}
return 0;
}
int mid = l + r >> 1, ans = 0;
if(tree[rt] >= val){
if(L <= mid){
ans = query(l ,mid,ls, L ,R , val);
}
if(ans == 0 && R > mid){
ans = query(mid + 1, r ,rs , L ,R ,val);
}
}
return ans;
}
int vis[N], sum[N];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i ++){
scanf("%d",&a[i]);
}
build(1, n , 1);
for(int i = n; i >= 1; i --){
if(a[i] > a[i + 1]){
vis[i] = i;
sum[i] = 1;
}
else {
vis[i] = vis[i + 1];
sum[i] = sum[i + 1] + 1;
int mx = a[vis[i]];
int l = vis[i] + 1, r = l;
while(a[i] <= a[r]){//确定最大区间
r = vis[r] + 1;
}
r -- ;
while(l <= r){
mx = query(1, n , 1 , l , r ,mx); //找到区间内从左开始找依次增大的值的位置
if(mx){
sum[i] ++;
}
else break;
vis[i] = mx;
l = mx + 1;
mx = a[mx];
}
}
}
int ans = 0;
for(int i = 1; i <= n; i ++){
ans += sum[i];
}
cout << ans << endl;
}