1.概念:
线段树将一个区间划分为多个单元区间,每个单元区间对应线段树中的一个叶子结点。
2.功能:
主要用于高效解决连续区间的动态查询问题。
3.性质:
根节点【m,n】 , 左子树【m,(m+n)/2】,右子树【(m+n)/2+1,n】
4.离散化
离散化是为了解决数据范围太大的情况,不改变其相对大小,用小数来替代大数,
个人模板:
线段树的定义:
const int maxn=200005; ///线段树的定义 int a[maxn]; struct node { int l,r; int CC;///CC可以代表sum,min,max等,依据自己定义, }tree[maxn*4];
建树
void build_tree(int root,int l,int r) { tree[root].l=l; tree[root].r=r; if(l==r) { tree[root].CC=a[l]; return; } int mid=(l+r)/2; build_tree(root*2,l,mid); build_tree(root*2+1,mid+1,r); tree[root].CC=max(tree[root*2].CC,tree[root*2+1].CC); }
单点更新(是区间更新的特例)
update(root , x , v) 即从根节点一直向下直到找到更新区间(Ax,Ax)即叶子节点
///单点更新 void update(int root,int where,int value) { int a,b,mid; a=tree[root].l; b=tree[root].r; if(where==a&&where==b) { tree[root].CC=value; return; } mid=(a+b)>>1; if(where<=mid) update(root*2,where,value); else update(root*2+1,where,value); tree[root].CC=max(tree[root*2].CC,tree[root*2+1].CC); }
区间查询
query(root ,l ,r )查询即从根节点处一直向下,不断拆分区间,不重不漏,比如查询[2,5]的min值,即[2,5]=[2]+[3,3]+[4,4]+[5,5]
int query(int root,int x,int y) { int a,b,mid; a=tree[root].l; b=tree[root].r; if(a>=x&&b<=y) ///当前节点完全被包含在查询区间内,即 x———a—————b————y return tree[root].CC; mid=(a+b)>>1; int res=-999; if(x<=mid)///查询左子树 res=max(res,query(root*2,x,y)); if(y>=mid+1)///查询右子树 res=max(res,query(root*2+1,x,y)); return res; }
区间修改(重要)
区间更新是指更新某个区间内 的 叶子节点的值 ,而涉及到的叶子节点的值的区间不止一个,而且叶子节点会回溯到相应的父亲节点,若是每次更新,则耗费时间,则需要用到延迟标记!
延迟标记:
每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记mark。
当我们再次修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p(即新节点的父亲节点)的标记,直到更新到完全包含于所更新区间的区间就停止。
即线段树的延迟标记就是标记以后以便下一次查询,遇到标记时更新此节点的子区间及其标记mark。如果需要继续向下查询,继续这样下去,直到查询结束。
const int maxn=100005; int a[maxn]; struct node { int r,l; int CC; int mark; }tree[maxn<<2]; void pushup(int root) { if(tree[root].l==tree[root].r) return; tree[root].CC=tree[root*2].l+tree[root*2+1].r; } void pushdown(int root,int l,int r) { int mid=(l+r)/2; ///更新左儿子 tree[root*2].mark+= tree[root].mark; tree[root*2].CC+=(mid-l+1)*tree[root].mark; ///更新右儿子 tree[root*2+1].mark+=tree[root].mark; tree[root*2+1].CC+=(r-(mid+1)+1)*tree[root].mark; ///消除父亲节点的标记 tree[root].mark=0; } ///建树 void build_tree(int root,int l,int r) { tree[root].mark=0; tree[root].l=l; tree[root].r=r; if(l==r) { tree[root].CC=a[l]; return ; } int mid=(l+r)>>1; build_tree(root*2,l,mid); build_tree(root*2+1,mid+1,r); pushup(root); } ///区间更新:在(L,R)区间的每个数上加上一个c void update(int root,int l,int r,int L,int R,int c) { if(L<=l&&R>=r) { tree[root].CC+=(r-l+1)*c; tree[root].mark+=c; return; } if(tree[root].mark!=0)///延迟标记 { pushdown(root,l,r); } int mid=(l+r)>>1; if(L<=mid)///查询左子树 update(root*2,l,mid,L,R,c); if(R>mid) update(root*2+1,mid+1,r,L,R,c); pushup(root); } ///区间查询 (L,R)区间的数的和 int query(int root,int l,int r,int L,int R) { if(L<=l&&R>=r) { return tree[root].CC; } if(tree[root].mark!=0)///如果有标记就需要往下更新 { pushdown(root,l,r); } int mid=(l+r)/2; int fin=0; if(L<=mid) { fin+=query(root*2,l,mid,L,R); } if(mid<R) { fin+=query(root*2+1,mid+1,r,L,R); } return fin; }