线段树(一) _概述 基本操做

线段树 Segment_treenode

网上有人把线段树翻译成 Interval_Treeios

Interval_Tree 是另一种数据结构 并且并不是二叉树
算法

这个是线段树的标准E文翻译数组

能够看wikipedia的原文 http://en.wikipedia.org/wiki/Segment_tree数据结构

顾名思义 线段树存储的是连续的线段而非离散的节点函数

先看一张经典的线段树图解post

这个就是标准的线段树ui

既然是树形结构 咱们就得先考虑怎么存储这棵树spa

分析线段树的定义翻译

*线段树是一棵二叉树 记为T(a, b)

*参数a,b表示区间[a,b] 其中b-a称为区间的长度 记为L

*线段树T(a,b)也可递归定义为

  -若L>1  [a, (a+b) div 2]为T的左儿子

         [(a+b) div 2,b]为T的右儿子

  -若L=1    T为叶子节点

能够获得一些基本性质

*线段树除最后一层外是满二叉树

*线段树是平衡的 高度是Log2L左右

如此咱们有2种存储方法

*直接用指针

定义节点

struct node{
	int L,r;
	int color;
}post[N<<2];

其中ls rs分别为左右儿子 l,r是区间的范围

真正实现时通常用数组模拟指针

咱们只需定义longint数组ls[]rs[] l[] r[]

*用*2和*2+1代替左右儿子指针

因为是除最后一层外是满二叉树

咱们能够向存储堆同样存储线段树

用l[]r[]来存储节点区间范围

x的左右儿子分别就是x*2和x*2+1

具体实现用位移代替乘2

这样乘法指针运算和上述数组调用同样 几乎不须要时间

具体用哪一种纯粹是我的喜爱 没什么区别

(下文中个人程序都是用的数组模拟 直接存储儿子指针)

接下来讨论线段树的具体操做

也就是维护这种数据结构的算法 (srO 数据结构+算法=程序 Orz)

总结起来就两个词 递归 & 分治

结合一个具体问题吧 PKU 2777

http://poj.org/problem?id=2777

这是线段树的入门题 至关经典

要求程序实现一个涂色的程序

支持对区间[A,B]涂C的颜色统计区间[A,B]的颜色种类

朴素的作法是用数组a[]存储下整个区间[0,100000]

而后循环涂色 循环查询 这样的复杂度是N*N 大大地TLE

咱们考虑用线段树处理这个区间问题

首先咱们得建树

先看程序

void Build(int L,int r,int id){
	post[id].L=L;
	post[id].r=r;
	post[id].color=1;
	if(L!=r){
		int mid=(L+r)>>1;
		Build(L,mid,id<<1);
		Build(mid+1,r,id<<1|1);
	}
}

*build函数是一个递归的过程 参数L,r表示当前创建区间[L,r]的节点

* L!=r 是递归的边界条件 即创建到叶子节点了

*根据线段树定义 分别递归创建左右儿子区间

-2*id,2*id+1分别为当前节点的左子树和右子树

  -注意使用运算提升效率 还需注意L r mid 皆为区间端点
 

其实上文中建好的线段树实际上是一个骨架

就至关于朴素作法中咱们还未操做的空数组 等待咱们给它刷颜色

既然要刷颜色 咱们就得存储各区间的颜色 给每一个节点新开一个域n来记录颜色

表如今数组模拟上就是新建数组n[]

n数组表明当前节点所表明区间的颜色

由于这个问题的染色是覆盖类型的染色

对一个区间染色天然把为当前区间的子区间也染色

因此是对子树染色而非区间染色

接着这样的思路 咱们能够写出以下程序

int mid=(post[id].r+post[id].L)>>1;
	if(post[id].L==L&&post[id].r==r){
		post[id].color=color;
		return;
	}
	if(post[id].color==color)
		return;
	if(post[id].color>0){
		post[id<<1].color=post[id<<1|1].color=post[id].color;
		post[id].color=0;
	}

*判断当前区间是否在须要覆盖的区间内 是就修改颜色

 

这里须要说明一下这种写法的正确性

不会出现[L,r]在[l[x],r[x]]外与当前区间没有交集的状况

首先在根节点处[L,r]和区间显然有交集

而后运用数学概括法的思路 说明当前节点区间和[L,r]有交集的时候 递归插入儿子也是保证和儿子区间有交集的

这样只要执行插入函数就有交集 就能保证程序正确性

给出全部和当前区间有交集的状况图 能够发现通过if语句判断 递归插入都保证仍是和儿子区间有交集

(黑色为当前区间 红色为欲染色区间 一共6种状况)

不难分析出这个插入函数的复杂度是O(N)级别的(须要遍历子树) 从常数上看比朴素还慢

可是不覆盖子树上的区间又会产生错误 咱们须要对插入进行改进

改进后 咱们的n[]数组不单记录一个节点的颜色 而是记录的子树的颜色

咱们看具体操做

*若是当前区间已经染色且颜色和欲染色一致 则直接退出(这句话能够不要)

*若是当前区间被彻底覆盖 就说明子树也被彻底覆盖了 直接给当前节点染色退出

*若是没有被彻底覆盖

  -就给先给左右儿子染色成当前节点的颜色 而后当前节点赋值为混合颜色=0

  -而后再递归染色左右子树

这样修改彻底覆盖的区间时就能够直接修改而后退出 不用遍历子树了

而没有彻底覆盖时 须要把颜色先下传给左右子树 再递归修改 保证子树颜色的正确性

这样咱们访问的区间总数就降到了O(LogN)级别个 比O(N)好了很多

这个实际上是一种最原始的Lazy-Tag思想

这种思想很重要 也比较难掌握 咱们之后详细讨论

给出改进后的代码


void update(int L,int r,int color,int id){
	int mid=(post[id].r+post[id].L)>>1;
	if(post[id].L==L&&post[id].r==r){
		post[id].color=color;
		return;
	}
	if(post[id].color==color)
		return;
	if(post[id].color>0){
		post[id<<1].color=post[id<<1|1].color=post[id].color;
		post[id].color=0;
	}

	if(r<=mid)
		update(L,r,color,id<<1);
	else if(L>mid)
		update(L,r,color,id<<1|1);
	else{
		update(L,mid,color,id<<1);
		update(mid+1,r,color,id<<1|1);
	}
}

最后就是统计了

 

统计相对很简单 一共30种颜色 用个Simple Hash便可

这时候咱们记录的混合颜色就有用了 用于判断

结构和插入差很少 不过递归的条件再也不是是否有交集而是是否为空节点


void query(int L,int r,int id){
	int mid=(post[id].L+post[id].r)>>1;
	if(post[id].color>0){
		visit[post[id].color]=1;
		return;
	}
	if(r<=mid)
		query(L,r,id<<1);
	else if(L>mid)
		query(L,r,id<<1|1);
	else{
		query(L,mid,id<<1);
		query(mid+1,r,id<<1|1);
	}
}

最后是个人AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define N 450000
struct node{
	int L,r;
	int color;
}post[N<<2];

bool visit[50];
void Build(int L,int r,int id){
	post[id].L=L;
	post[id].r=r;
	post[id].color=1;
	if(L!=r){
		int mid=(L+r)>>1;
		Build(L,mid,id<<1);
		Build(mid+1,r,id<<1|1);
	}
}
void update(int L,int r,int color,int id){
	int mid=(post[id].r+post[id].L)>>1;
	if(post[id].L==L&&post[id].r==r){
		post[id].color=color;
		return;
	}
	if(post[id].color==color)
		return;
	if(post[id].color>0){
		post[id<<1].color=post[id<<1|1].color=post[id].color;
		post[id].color=0;
	}

	if(r<=mid)
		update(L,r,color,id<<1);
	else if(L>mid)
		update(L,r,color,id<<1|1);
	else{
		update(L,mid,color,id<<1);
		update(mid+1,r,color,id<<1|1);
	}
}
void query(int L,int r,int id){
	int mid=(post[id].L+post[id].r)>>1;
	if(post[id].color>0){
		visit[post[id].color]=1;
		return;
	}
	if(r<=mid)
		query(L,r,id<<1);
	else if(L>mid)
		query(L,r,id<<1|1);
	else{
		query(L,mid,id<<1);
		query(mid+1,r,id<<1|1);
	}
}
int main(){
	int L,v,n,a,b,c,i,j;
	char tmp[3];
	int sum=0;
	while(scanf("%d%d%d",&L,&v,&n)!=EOF){
		
		Build(1,L,1);
		for(i=0;i<n;i++){
			scanf("%s",tmp);
			if(tmp[0]=='P'){
				scanf("%d%d",&a,&b);
				sum=0;
				memset(visit,0,sizeof(visit));
				query(a,b,1);
				for(j=1;j<=v;j++)
					if(visit[j])
						sum++;
				printf("%d\n",sum);
			}else{
				scanf("%d%d%d",&a,&b,&c);
				update(a,b,c,1);
			}
		}
	}
}
相关文章
相关标签/搜索