【算法】值域线段树

值域线段树就是线段,只不过它的节点代表的东西与普通的线段树不同。

比如给了一个数列,值域线段树的每个节点有三个性质: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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有 $n$ 个点在数轴上,每个点有一个权值 $a_i$,你需要支持以下操作: - 修改一个点的权值。 - 给出 $l,r,k$,询问在区间 $[l,r]$ 中,权值严格大于 $k$ 的点的个数。 输入格式 第一行一个正整数 $n(1\leq n\leq 5\times10^5)$。 第二行 $n$ 个整数 $a_i(|a_i|\leq 10^9)$,表示每个点的权值。 第三行一个正整数 $m(1\leq m\leq 5\times10^5)$。 接下来 $m$ 行,每行一个操作,格式如下: - “Q l r k” 表示询问区间 $[l,r]$ 中,权值严格大于 $k$ 的点的个数。 - “C x y” 表示将第 $x$ 个点的权值修改为 $y$。 输出格式 对于每个询问操作,输出其结果。 输入样例 5 0 1 2 3 4 4 Q 2 5 3 C 4 6 Q 1 5 2 Q 3 4 4 输出样例 1 2 0 算法1 线段树(动态开点) 线段树的思想是把区间分成若干个小区间,每个小区间对应一段线段。对于每个线段,维护一些信息,例如区间和、区间最大值等等。 对于这道题目,我们可以按照值域线段树的思想,将区间对应到值域上。即将整个区间 $[0,n-1]$ 对应到值域上,建立一棵值域线段树。对于线段树上的每个节点,维护该节点对应的区间内权值大于某个值 $k$ 的点的个数。当然,对于叶子节点,该值就是 $0$ 或 $1$。 对于一个查询操作 $Q(l,r,k)$,需要在值域线段树上找到 $[l,r]$ 对应的区间,然后查询该区间内权值大于 $k$ 的点的个数。这个可以通过线段树的区间查询操作实现。 对于一个修改操作 $C(x,y)$,需要在值域线段树上找到 $x$ 对应的叶子节点,然后修改该叶子节点的值为 $y$,然后向上更新整个线段树,直到根节点。 时间复杂度 对于每次修改和查询操作,都需要在值域线段树上查询或修改,时间复杂度是 $O(\log n)$。总时间复杂度是 $O(m\log n)$。 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值