Magic-Segment Tree

· · 算法·理论

Segment Tree-线段树

前言:线段树为OI生涯中超级重要的一个板块,应用范围快,易思考的数据结构,所以一定要完全掌握

Build-建树

某些情况要对区间(包括单个的叶子节点)进行初始化时,就一般要建一棵树。

考虑如何建树,我们可以用 l,r 代表当前节点表示的区间,当l=r时,必定为叶子节点。然后从叶子节点向父节点返回,很显然的看出父节点等于两个子节点合并,大多数采用递归。

如以 a_1 \sim a_n 我们维护区间和,建树就如下:

void build(int l,int r,int root){
    if(l==r){
        tree[root]x=1;
        return ;
    }
    auto lp=(root<<1),rp=(lp+1),mid=(l+r)/2;
    build(l,mid,lp),build(mid+1,r,rp);
    tree[root]=calculation(tree[lp],tree[rp]);
}

Lazy Tag-区间修改

单点修改有兴趣可以自己搜,但我不太建议,毕竟区间修改的左端点等于有端点就是单点了。

众所周知单点修改为 O(log ~ n),如果使用多次单点修改以此修改区间,时间复杂度很提高个 len。这时候我们设想可不可以将要修改的区间里的点父节点加上一个标记(闲话:因为这个标记优化了区间修改,而且写起也简单,懒人专用),然后我们每次将父节点的标记子节点传递,并将子节点的值进行修改(push_down)。如当此时当前区间属于所需修改区间则直接修改当前节点的值,如果此时区间超过了所需修改区间则不修改。就做到了O(log~n)的复杂度区间查询。每次由此区间分别往两边搜索,相当于遍历一遍树,但不符合区间的会直接跳过,所以相当于搜索一遍高为log~n

void push_down(int l,int r,int root){
    auto lp=(root<<1),rp=(lp|1),mid=(l+r)/2;
    tree[lp].tag+=tree[root].tag,tree[rp].tag+=tree[root].tag;
    tree[lp].x+=(mid-l+1)*tree[root].tag;
    tree[rp].x+=(r-mid)*tree[root].tag;
    // cerr<<"Fuck Colin:"<<tree[root].x<<" "<<tree[lp].x<<" "<<tree[rp].x<<endl;
    tree[root].tag=0;
}
void modify(int lc,int rc,int l,int r,zjj x,int root){
    if(l>rc||r<lc) return ;
    if(l<=lc&&r>=rc){
        tree[root].tag+=x;
        tree[root].x+=x*(rc-lc+1);
        return ;
    }
    auto lp=(root<<1),rp=lp+1,mid=(lc+rc)>>1;
    push_down(lc,rc,root);
    modify(lc,mid,l,r,x,lp),modify(mid+1,rc,l,r,x,rp);
    tree[root]=calculation(tree[lp],tree[rp]);
}

qry-查询

modify差不多,不过是qry搜索到属于所需区间的子区间时直接返回当前节点的值,如果与所需区间无相交部分则对所需区间没有任何贡献返回0