线段树导例

 

问题:直线上分布一系列的整数格点12n,有m个整数线段[ai,bi]1<=ai<=bi<=ni=1,...,m),求q个区间[sj,tj]分别被线段覆盖的总长度,即[ai,bi]的并集中包含的[sj,tj]中整数格点数。

例如:n=12m=5m个线段是[1,5][4,6][9,9][1,2][8,10]

[1,12]被覆盖总长度是9,而[2,9]被覆盖总长度为7

 

问题 :直线上分布一系列的整数格点12n,有m个整数线段[ai,bi]1<=ai<=bi<=ni=1,...,m),求任意一个区间[sj,tj]被这些线段覆盖的总长度,即它们的并集包含的整数格点个数。

思路:

首先在附加数据域设一个cover标记来跟踪一个区间是否被全覆盖,也可再设一个length跟踪覆盖长度(本例不设)。Node如下:

    struct Node {

  int left, right;

  int cover;    // 标记, 1表示被全覆盖

    };

 

然后实现三个模块:建立、更新、查询。

建立:建立线段树,顺便将cover初值置0

更新:根据每个线段[ai,bi]对线段树进行更新,记录覆盖情况。

查询:对任意区间的查询,由它的拼接元线段和标记返回查询结果。

 

样例输入:

12 5 2

1 5

4 6

9 9

1 2

8 10

1 12

2 9

 

样例输出:

9

7

code:

采用左闭右开的区间表示

#include <iostream>
const int MAXN =1000;

struct node{
    //覆盖范围
	int cover;
    //左区间闭区间
	int left;
    //右区间开
	int right;
};
//最多四倍空间
node st[MAXN<<2];
// 节点建树
//start 开始的建树区间
//end  末尾区间
//其区间对应的节点id
void Build(int start,int end,int id){
	st[id].cover = 0;
	st[id].left = start;
	st[id].right = end;
    //结束条件,因为左闭右开,所以当插值为1的时候已经到子节点了 比如 [3,4),其实对应的就是节点3而已。
	if(end - start == 1)return;
    
	int mid = (start+end)/2;
	Build(start,mid,id*2);
	Build(mid,end,id*2+1);
}
// 节点更新
//start 开始的更新间
//end  末尾更新区间
//其区间对应的节点id   
//注意,这个时候线段树已经建好了,对应的区间是要更新的区间,而不是线段树每一个节点的区间
void Update(int start,int end,int id){
	//想要更新的区间在这个子树的节点范围之外
	if(end<st[id].left || start>st[id].right)return;
    //想要更新的区间正好覆盖了这个子树的范围,那么就可以更新
	if(start<=st[id].left && end>=st[id].right)st[id].cover = 1;
	//退出条件,如果是子节点了,就不要再递归了
	if(st[id].right - st[id].left ==1) return;
	Update(start,end,id*2);
	Update(start,end,id*2+1);
}
// 节点查询
//start 开始的查询间
//end  末尾的查询区间
//其区间对应的节点id   
//注意,这个时候线段树已经建好了,对应的区间是要更新的区间,而不是线段树每一个节点的区间
int Query(int start,int end,int id){
	if(end<st[id].left || start>st[id].right)return 0;
	
	if(start<=st[id].left && end>=st[id].right &&st[id].cover == 1){
//		printf("left:%d right:%d\n", st[id].left,st[id].right);
		return st[id].right - st[id].left;
	}
	if(st[id].right - st[id].left ==1) return 0;
	int ans = 0;
	ans+=Query(start,end,id*2);
	ans+=Query(start,end,id*2+1);
	return ans;
}

int main(int argc, char** argv) {
	int n, m, q;
	int a, b, s, t;
	scanf("%d%d%d", &n, &m, &q);
    //左臂右开,所以建树范围是[1,n+1)	 起点是根节点1
	Build(1, n+1,1);
	for (int i = 0; i < m; i++) {
		scanf("%d%d", &a, &b);
        //左臂右开,所以范围是[a,b+1)  起点是根节点1
		Update(a, b+1,1);
	}
	for (int i = 0; i < q; i++) {
		scanf("%d%d", &s, &t);
        //左臂右开,所以范围是[s,t+1)  起点是根节点1
		printf("%d\n", Query( s, t+1,1));
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值