值域线段树就是线段,只不过它的节点代表的东西与普通的线段树不同。
比如给了一个数列,值域线段树的每个节点有三个性质:l,r,val,其中val代表这组数列中数值在 l 和 r 之间的数的个数。
值域线段树结点下标不连续。
结合[BJOI2016]回转寿司分析
题目大意:
现在有一个 N 个数组成的数列 {ai} ,求有多少段的和在 [L,R] 内。
1<=N<=105 , |ai| <=105 , 1<=L,R<=109
我们记前 i 个数之和为 s[i]。
满足条件的一段区间 [l,r] 符合 L <= s[r]-s[l-1] <= R
移项,得 s[r]-R <= s[l-1] <= s[r]-L
这样,我们只需找到对每一个 r ,有多少个 l-1 满足上式,加起来就好了。
我们可以在每次加入 s[r] 之前,在线段树中找符合条件的 s[l-1] 的个数。
但现在又有一个问题,s[i] 的范围是 -1010~1010,不可能见一个有这么多叶子结点的线段树。s[i] 最多有 105 个,剩下的没有取到的值不建立节点,这样空间就足够了,这也就决定了节点下标是不连续的。
大概思路就是这样,具体细节见下面的代码。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+10;
const LL INF=1e10;
int n, l, r;
LL s[N];
int cnt=0;
class Node
{
public:
int num, lson, rson;
Node(int num=0, int lson=0, int rson=0){ this->num=num; this->lson=lson; this->rson=rson; }
}nd[N*70];
void update(int &rt, LL l, LL r, LL val) //加入一个值
{
if(!rt) rt=++cnt;
nd[rt].num++;
if(l==r) return;
LL mid=l+r>>1;
if(val<=mid) update(nd[rt].lson,l,mid,val);
else update(nd[rt].rson,mid+1,r,val);
}
LL query(int rt, LL l, LL r, LL L, LL R) //查询当前数值在[L,R]内的数的个数
{
if(!rt||l>R||L>r) return 0;
if(l>=L&&r<=R) return nd[rt].num;
LL mid=l+r>>1;
if(mid>=R) return query(nd[rt].lson,l,mid,L,R);
else if(mid<L) return query(nd[rt].rson,mid+1,r,L,R);
else return query(nd[rt].lson,l,mid,L,mid)+query(nd[rt].rson,mid+1,r,mid+1,R);
}
int main()
{
scanf("%d%d%d", &n, &l, &r);
for(int i=1; i<=n; ++i)
{
scanf("%lld", s+i);
s[i]+=s[i-1];
}
LL res=0;
int root=++cnt;
update(root,-INF,INF,0);
for(int i=1; i<=n; ++i)
{
res+=query(root,-INF,INF,s[i]-r,s[i]-l);
update(root,-INF,INF,s[i]);
}
printf("%lld\n", res);
return 0;
}