其他分享
首页 > 其他分享> > 树状数组&线段树

树状数组&线段树

作者:互联网

树状数组与线段树

树状数组

$ 5 \times 5$

常见用处:可以快速解决部分基于区间上的更新以及求和问题。

img

由大节点存储小节点信息,查询时只需要查询大节点即可

最上面的块,$c_8$管理整个数组a($c_1,c_2,c_3,c_4,c_5,c_6,c_7,c_8$八个数),$c_4$管理的是$a_1,a_2,a_3,a_4$。其余同理。


基本用法与操作(单点修改&区间查询)


区间加&区间求和(区间修改&区间查询

维护a的差分数组b,若要求一个前缀r的前缀和,可推导出r的前缀和与差分数组的关系。

推导:

$\sum_{i=1}^ra_i$

$=\sum_{i=1}r\sum_{j=1}ib_i$

$=\sum_{i=1}^rb_i\times(r-i+1)$

$=(r+1)\times \sum_{i=1}rb_i-\sum_{i=1}r(b_i\times i)$

需要两个树状数组来维护:一个$t_1$维护$b_i$,一个$t_2$维护$i\times b_i$。

int t1[MAXN],t2[MAXN],n;

inline int lowbit(int x){return x&(-x);}

void add(int k,int v)//k的前缀和增加v
{
    int v1=k*v;
    while(k<=n){
        t1[k]+=v,t2[k]+=v1;
        k+=lowbit(k);
    }
}

void getsum(int *t,int x){
    int ans=0;
    while(x>=0){
        ans+=t[x];
        x-=lowbit(x);
    }
    return ans;
}

void add1(int l,int r,int v){
    add(l,v),add(r+1,-v);
}

long long getsum1(int l,int r){
    return (r+1ll)*getsum(t1,r)-1ll*l*getsum(t1,l-1)-
        	(getsum(t2,r)-getsum(t2,l-1));
}

线段树


基本结构&建树

img

初始化建树

当区间长度为1时,可以根据a数组相应位置的值初始化该节点。否则从中件分割为两个子区间,递归建树,最后合并。

void build(int s,int t,int p){
    //对[s,t]区间建树,当前根编号为p
    if(s==t){
        d[p]=a[s];
        return;
    }
    int m=s+((t-s)>>1);
    build(s,m,p*2),build(m+1,t,p*2+1);
    //2*p是p的左儿子,2*p+1是p的右儿子。
    d[p]=d[p*2]+d[p*2+1]; 
}

线段树的区间查询

查询区间线段树有可以直接得到解,若无,可以拆解区间再求和。


线段树的区间修改&懒惰标记

如果要修改区间[l,r],把包含区间[l,r]中的节点都遍历一次、修改一次,时间复杂度无法承受。因此,引入一个懒惰标记

  • 简单来说就是延迟对节点的修改,要修改时做标记,再下一次访问带有标记的节点时才修改。

引入数组t,每个节点增加$t_i$。

img

现在准备把[3,5]上的每个数都加上5。类似区间查询,找到了两个极大区间[3,3]和[4,5](分别对应线段树3号和5号点),并标记对应t。

img

虽然3号节点信息虽然被修改了,但其左右儿子节点还未更新。在三号节点做着标记。以便以后要访问其子节点时顺便更新,不浪费多余时间现在更新。

void updateUtil(int l,int r,int c,int s,int t,int p){
    if(l<=s&&r>=t){
        st[p]+=(t-s+1)*c;//st  segment_tree
        b[p]+=c;
        return;
    }
    int m=s+((t-s)>>1);
    if(b[p]){
        st[2*p]+=(m-s+1)*b[p],st[2*p+1]=(t-m)*b[p];
        b[2*p]+=b[p],b[2*p+1]=b[p];
        b[p]=0;
    }
    if(m>=l)
        updateUtil(l,r,c,s,m,2*p);
    if(m<r)
        updateUtil(l,r,c,m+1,t,2*p+1);
    d[p]=d[2*p]+d[2*p+1];
}
LL getsum(int l,int r,int s,int t,int p){
    if(t<l||s>r)return 0L;
    if(s>=l&&t<=r)return st[p];
    int m=s+((t-s)>>1);
    //多了一个pushdown操作
    if(b[p]){
        st[2*p]+=(m-s+1)*b[p],st[2*p+1]+=(t-m)*b[p];
        b[2*p]+=b[p],b[2*p+1]+=b[p];
        b[p]=0;
    }
    
    return getsum(l,r,s,m,2*p)+getsum(l,r,m+1,t,2*p+1);
}

标签:return,树状,int,线段,getsum,数组,区间,节点
来源: https://www.cnblogs.com/ckcyi/p/15712986.html