题目链接:https://ac.nowcoder.com/acm/contest/883/G
题目大意:求区间内有多少个区间满足最大值的两倍小于等于区间和
题目思路:使用启发式合并,先使用st表得到最大值的位置,然后看左右两边哪边小枚举哪边,二分另一边符合要求的位置,然后加上符合要求的区间数量,这样包含该最大值的所有情况就出来了,继续分治两边的情况,得到两边的解加上,就是该区间的情况。
这里可以加入一个优化,就是两倍的最大值是一个定值,所以假设我们枚举的是左区间,那右边二分得到的位置一定是逐渐递增的,所以二分的左区间可以继承上一次二分得到的答案
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
const int MAXN = 3e5+5;
const int MOD =1e9+7;
int t,n,a[MAXN],f[MAXN][22],pos[MAXN][22];
ll sum[MAXN];
template <class T>
inline bool scan_d(T &ret){
char c;int sgn;
if(c=getchar(),c==EOF)return 0;
while(c!='-'&&(c<'0'||c>'9'))c=getchar();
sgn=(c=='-')?-1:1;
ret=(c=='-')?0:(c-'0');
while(c=getchar(),c>='0'&&c<='9')ret=ret*10+c-'0';
ret*=sgn;
return 1;
}
void ST_prework(){
rep(i,1,n)f[i][0]=a[i],pos[i][0]=i;
int t=log(n)/log(2)+1;
rep(j,1,t-1){
rep(i,1,n-(1<<j)+1){
if(f[i][j-1]>f[i+(1<<(j-1))][j-1]){
f[i][j]=f[i][j-1];
pos[i][j]=pos[i][j-1];
}
else{
f[i][j]=f[i+(1<<(j-1))][j-1];
pos[i][j]=pos[i+(1<<(j-1))][j-1];
}
}
}
}
int ST_query(int l,int r){
int k=log(r-l+1)/log(2);
if(f[l][k]>f[r-(1<<k)+1][k]){
return pos[l][k];
}
else{
return pos[r-(1<<k)+1][k];
}
}
ll solve(int l,int r){
if(l>=r)return 0;
ll anss=0;
int pos=ST_query(l,r);
if(r-pos>pos-l){
int ans=-1;
int L=pos,R=r;
rep(i,l,pos){
if(ans==-1)L=pos;
else L=ans;
R=r;
ans=-1;
while(L<=R){
int mid=(L+R)>>1;
if(sum[mid]-sum[i-1]>=a[pos]*2){
R=mid-1;
ans=mid;
}
else{
L=mid+1;
}
}
if(ans!=-1)
anss+=r-ans+1;
}
}
else{
int ans=-1;
int L=l,R=pos;
rep(i,pos,r){
if(ans==-1)L=l;
else L=ans;
R=pos;
ans=-1;
while(L<=R){
int mid=(L+R)>>1;
if(sum[i]-sum[mid-1]>=a[pos]*2){
L=mid+1;
ans=mid;
}
else{
R=mid-1;
}
}
if(ans!=-1)
anss+=ans-l+1;
}
}
return anss+solve(l,pos-1)+solve(pos+1,r);
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
sum[0]=0;
rep(i,1,n){
scan_d(a[i]);
sum[i]=sum[i-1]+a[i];
}
ST_prework();
printf("%lld\n",solve(1,n));
}
return 0;
}