线段树入门

把我csdn的blog搬过来的((将就看

 

线段树

想学主席树,然后开了一篇题解,看到“学习可持久化线段树之前一定要学懂线段树”这句话我只好把线段树又学了一遍((
这块从昨天到今天学了四个小时,做了三页纸笔记,所以有错的可能性不大(但是wtcl,所以还是有很大可能的

 

概括

线段树的理解,按照书上的((

  1. 线段树的每个节点都代表一个区间;
  2. 线段树具有唯一的根节点,代表的区间是整个统计范围;
  3. 线段树的每个叶节点都代表一个长度为1的元区间[x,x];
  4. 对于每个内部节点[l,r],它的左子节点是[l,mid],右子节点是[mid+1,r],其中mid=(l+r)/2;

图我就不放了

另一个点就是”父子2倍“,即节点i的权值=左儿子权值+右儿子的权值,举个栗子,节点i的权值编号p,左儿子权值即p*2,右儿子权值即p * 2+1(应该没说错吧;

 

建树

先讲一下线段树的建树。
struct数组存储我就先略过了;

 

上代码:

void build (int p,int l,int r) {
	t[p].l=l,t[p].r=r;    //节点p代表区间[l,r];
	if (l==r){
		t[p].dat=a[l];
		return;			  //叶节点; 
	} 
	int mid=(l+r)/2;	  //折半(根据“父子二倍”,求mid;
	build(p*2,l,mid);	  //构造左子节点,即[l,mid],编号p*2;
	build(p*2+1,mid+1,r); //同上一步,构造右子节点,即[mid+1,r],编号p*2+1;
	t[p].dat=max(t[p*2].dat,t[p*2+1].dat);
	//从下往上传递信息; 
}
build (1,1,n); //调用入口;

  

单点修改

所谓单点修改,就是:

  1. 从根节点出发,递归找到代表区间[x,x]的叶节点;
  2. 更新(从下往上)[x,x]以及其所有祖先节点上的信息;
  3. 时间复杂度为O(logN);

 

上代码:

void change (int p,int x,int v) {
	if (t[p].l==t[p].r) {
		t[p].dat=v;
		return;			//找叶节点; 
	}
	int mid=(t[p].l+t[p].r)/2;	//折半; 
	
	if (x<=mid)			//找x所属的区间;
		change(p*2,x,v); 
	else
		change(p*2+1,x,v); 
	t[p].dat=max(t[p*2].dat,t[p*2+1],dat); 
} 

change(1,x,v); //调用入口; 

  

 

区间查询

递归:

  1. [l,r]完全覆盖了当前节点代表的区间,回溯,该节点的dat值为候选答案;
  2. 左子节点与[l,r]有重叠部分,递归访问左子节点;
  3. 右子节点与[l,r]有重叠部分,递归访问右子节点;

 

代码:

int ask(int p,int l,int r) {
	if (l<=t[p].l &&r>=t[p].r)	return t[p].dat; //完全包含; 
	int mid=(t[p].l+t[p].r)/2; 
	int val=a(1<<30);
	if (l<=mid) val=max(val,ask(p*2,l,r)); //与左子节点有重叠;
	if (r>mid)	val=max(val,ask(p*2+1,l,r)); //与右子节点有重叠;
	return val; 
}
cout<<ask(1,l,r)<<endl; //调用入口; 

  

还有两个点,即区间修改&延迟标记;
区间修改就是结合单点修改和区间查询…
延迟标记是“在回溯前向节点p加一个标记,标识“该节点曾被修改,但子节点尚未更新””。
这边就不敲代码了((我太菜了怕出错

 

总结

有点虎头蛇尾,毕竟是自己对线段树的总结可能会有误;

本篇blog应该适用于线段树入门QAQ

 

谢谢大家耐心看完awa

 

Tags: