2019牛客暑期多校训练营(第三场)G Removing Stones(启发式分治)

题目链接: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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值