标签:log 公约数 注意 检查 执行 长度 style 特征 fine
摘自《算法竞赛进阶指南》。
线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计。
线段树的基本特征:
1.线段树的每个节点都代表一个区间。
2.线段树具有唯一的根节点,代表的区间是整个统计范围,如[1,N]。
3.线段树的每个叶节点都代表一个长度为1的元区间[x,x]。
4.对于每个内部节点[l,r],它的左子节点是[l,mid],右子节点是[mid+1,r],其中mid=(l+r)/2(向下取整)。
线段树的节点编号方法:“父子二倍”节点编号法
1.根节点编号为1。
2.编号为x的节点的左子节点的编号为x*2,右子节点编号为x*2+1。
注意:保存线段树的数组长度要不小于4N才能保证不会越界。(N为叶节点数)
struct SegmenTree{
int l,r;
int dat;
}t[N*4];//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;//折半
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]的叶节点,然后从下往上更新[x,x]以及它的所有祖先节点上保存的信息。
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)change(p*2,x,v);//x属于左半区间 else change(p*2+1,x,v);//x属于右半区间 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=-(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;//调用入口
该查询过程会把询问区间[l,r]在线段树生分成O(log N)个节点,取它们的最大值作为答案。
原因:在每个节点[pl,p_r]上,设mid=(pl+pr)/2(向下取整),可能会出现以下几种情况:
1.l≤pl≤pr≤r,即完全覆盖了当前节点,直接返回。
2.pl≤l≤pr≤r,即只有l处于节点之中。
(1)l>mid,只会递归右子树。
(2)l≤mid,虽然递归两棵子树,但是右子节点会在递归后直接返回。
3.l≤pl≤r≤pr,即只有r处于节点之中,与情况2类似。
4.pl≤l≤r≤r,即l与r都处于节点之中。
(1)l,r都位于mid的一侧,只会递归一棵子树。
(2)l,r分别位于mid的两侧,递归左右两棵子树。
我们在修改指令时,可以在l≤pl≤pl≤r的情况下立即返回,只不过在回溯之前向节点p增加标记,标识“该节点曾经被修改,但其子节点尚未被更新”。
在后续的指令中,需要从节点p向下递归,我们再检查p是否具有标记。若有标记,就根据标记信息更新p的两个子节点,同时为p的两个子节点增加标记,然后清除p的标记。
这样一来,每条修改的指令的时间复杂度从O(N)降到了O(log N)。
【例题】:一个简单的整数问题
主要代码如下:
#define 100000+10
struct N SegmentTree{
int l,r;
long long sum,add;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define add(x) tree[x].add
#define sum(x) tree[x].sum
}tree[N*4];
int a[N],n,m;
void build(int p,int l,int r){
l(p)=l,r(p)=r;
if(l==r){sum(p)=a[l];return;}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
sum(p)=sum(p*2)+sum(p*2+1);
}
void spread(int p){
if(add(p)){//节点p有标记
sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1);//更新左子节点信息
sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1);//更新右子节点
add(p*2)+=add(p);//给左子节点打延迟标记
add(p*2+1)+=add(p);//给右子节点打延迟标记
add(p)=0;//清除b的标记
}
}
void change(int p,int l,int r,int d){
if(l<=l(p)&&r>=r(p)){//完全覆盖
sum(p)+=(long long)d*(r(p)-l(p)+1);//更新节点信息
add(p)+=d;//给节点打延迟标记
return;
}
spread(p);//下传延迟标记
int mid=(l(p)+r(p))/2;
if(l<=mid)change(p*2,l,r,d);
if(r>mid)change(p*2+1,l,r,d);
sum(p)=sum(p*2)+sum(p*2+1);
}
long long ask(int p,int l,int r){
if(l<=l(p)&&r>=r(p))return sum(p);
spread(p);//下传延迟标记
int mid=(l(p)+r(p))/2;
long long val=0;
if(l<=mid)val+=ask(p*2,l,r);
if(r>mid)val+=ask(p*2+1,l,r);
return val;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
while(m--){
char op[2];int l,r,d;
cin>>op>>l>>r;
if(op[0]==‘C‘){
cin>>d;
change(1,l,r,d);
}
else cout<<ask(1,l,r);
}
}
标签:log 公约数 注意 检查 执行 长度 style 特征 fine
原文地址:https://www.cnblogs.com/zhengchang/p/xianduanshu1.html