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\n",ans); } }