题意:给定一个序列,给定区间[L,R] ,问序列有多少子串的和属于这个区间。
分析:
我们先处理得到前缀和数组,pre[] ,子串[l,r]的和即:pre[r]-pre[l-1]
问题即:对于L<=pre[r]-pre[l-1]<=R (1<=l<=r<=n ),有多少对l r满足它
我们转化一下变成:pre[r]-R<=pre[l-1]<=pre[r]-L (1<=l<=r<=n )
到这里可以想到一种思路:
我们枚举r同时把pre数组元素存进桶里,
对于每个r都能得到区间[pre[r]-R , pre[r]-L ] ,我们查桶里这个区间里面有多少个pre数组元素即可。 但是如果暴力查询的话明显时间和空间上都不行。
想到可以用权值线段树来维护桶:
我们枚举r查询当前线段树中桶的值,查询结束后把pre[r]插入线段树。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+7;
const ll INF = 1e10;
int tree[maxn*150],lc[maxn*150],rc[maxn*150];//动态开点
int root=1,cnt=1;
ll sum[maxn],L,R;
int n;
inline void pushup(int &rt)
{
tree[rt]=tree[lc[rt]]+tree[rc[rt]];
return ;
}
inline void updata(int &rt,ll l,ll r,ll x)
{
if(!rt) rt=++cnt;
if(l == r)
{
tree[rt]++;
return ;
}
ll mid=(l+r)>>1;
if(x<=mid) updata(lc[rt],l,mid,x);
else updata(rc[rt],mid+1,r,x);
pushup(rt);
return ;
}
inline int query(int rt,ll l,ll r,ll vl,ll vr)//区间[vl,vr]数出现的个数
{
if(r<vl || l>vr || !rt) return 0;
if(vl<=l && r<=vr) return tree[rt];
ll mid=(l+r)>>1;
return query(lc[rt],l,mid,vl,vr)+query(rc[rt],mid+1,r,vl,vr);
}
int main()
{
scanf("%d %lld %lld",&n,&L,&R);
for(int i=1;i<=n;i++)
{
scanf("%lld",sum+i);
sum[i]+=sum[i-1];
}
updata(root,-INF,INF,sum[0]);//l==1的情况,[1,r]的子串和
ll ans=0;
for(int i=1;i<=n;i++)
{
ans+=query(root,-INF,INF,sum[i]-R,sum[i]-L);
//l-1在[0,r)内
updata(root,-INF,INF,sum[i]);
}
printf("%lld\n",ans);
return 0;
}