【算法】值域線段樹

值域線段樹就是線段,只不過它的節點代表的東西與普通的線段樹不同。

比如給了一個數列,值域線段樹的每個節點有三個性質: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章