2019牛客暑期多校训练营(第三场)G.Removing Stones(ST+分治)

题意:给你n堆石子 每堆有ai个 现在问你有多少个连续的区间保证最大值小于等于该区间和的两倍

思路:我们可以考虑每个区间的分割点 总是该区间的最大值 所以我们只要ST找到该区间的最大值 然后每次都枚举较小的半区间 二分找到刚号满足的区间 这样我们就可累加个数了

注意边界的情况 本人用lower_bound找bug找到自闭

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
const int inf = 0x3f3f3f3f;
typedef long long ll;
const ll mod = 1e7+9;
int a[N],f[N][30],mm[N];
ll sum[N];
int n;
int max(int i,int j){
    if(a[i]>=a[j]) return i;
    else return j;
}
void preST(int len){
    mm[0]=-1;
    for(int i=1;i<=len;i++){
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
        f[i][0]=i;
    }
    for(int j=1;j<=mm[len];j++)
        for(int i=1;i<=(len-(1<<j)+1);i++)
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
//[i,i+2^j-1]最大值即是 i~i+2^(j-1)和 i+2^(j-1)~i+2^(j-1)+2^(j-1) 这两半区间的较大值
}
int queryST(int l,int r){
    int k=mm[r-l+1]; //保证k满足 2^k<r+l-1<=2^(k+1)
    return max(f[l][k],f[r-(1<<k)+1][k]);
}
ll ans=0;
void solve(int l,int r){
    if(l>=r) return ;
    int mid=(l+r)>>1;
    int x=queryST(l,r);
    if(x<=mid){
        for(int i=l;i<=x;i++){
            int po=lower_bound(sum+x,sum+r+1,sum[i-1]+2*a[x])-sum;
            ans+=(r-po+1);
        }
    }else{
        for(int i=x;i<=r;i++){
            int po=upper_bound(sum+l-1,sum+x,sum[i]-2*a[x])-sum;
            ans+=(po-l+1);
        }
    }
    solve(l,x-1);
    solve(x+1,r);
}
int main(){
//    ios::sync_with_stdio(false);
//    cin.tie(0); cout.tie(0);
    int t; scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        ans=0;
        for(int i=1;i<=n;i++)
            scanf("%d",a+i),sum[i]=sum[i-1]+a[i];
        preST(n);
        solve(1,n);
        printf("%lld
",ans);
    }
}
原文地址:https://www.cnblogs.com/wmj6/p/11290864.html