1. 程式人生 > 實用技巧 >樹狀陣列 線段樹

樹狀陣列 線段樹

樹狀陣列

有一個A陣列

a[1],a[2],a[3],a[4],…………

有一個C陣列 是樹狀數組裡存的東西,每個結點掌管[x-lowbit(x)+1,x]

c[1]=a[1]
c[2]=a[1]+a[2]
c[3]=a[3]
c[4]=a[1]+a[2]+a[3]+a[4]
c[5]=a[5]
c[6]=a[5]+a[6]

單點修改區間查詢

change: Ax增加val,Ax及其父節點都要增加val。

search: 詢問[l,r]的權值和 可以用統計好的字首和陣列sum進行sum[r]-sum[l-1]

​ 這裡用到的sum,從x開始加,每次加完將x=x-lowbit(x),直到x為1

//lowbit(i)= ((i)&(-i))
void change(int x,int val)
{
	for(int i=x;i<=n;i+=low(i))
        c[i]+=val;
}

void search(int x)
{
    int sum=0;
    for(int i=x;i;i-=lowbit(i))
        sum+=c[i];
    return sum;
}

區間修改單點詢問

區間修改:差分思想,l處+val,r+1處-val

單點詢問:

int main() {
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),sum[i]=sum[i-1]+a[i];
	for(int i=1;i<=m;i++) {
		int opt=read(),l=read(),r=read();
		if(opt==1) {//區間加
			ll k=read();
			T1.add(l,k);T1.add(r+1,-k);
			T2.add(l,l*k);T2.add(r+1,-(r+1)*k);
		}
		else {//區間查詢
			ll ans=sum[r]-sum[l-1];
			ans+=(r+1)*T1.query(r)-T2.query(r);
			ans-=l*T1.query(l-1)-T2.query(l-1);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

https://www.cnblogs.com/jason2003/p/9676729.html
x&(~x+1
t[x]=lowbit(x)
差分陣列d[i]=a[i]-a[i-1]; a[0]=0;__>a[i]=d[1]+……+d[i]

線段樹

一個節點p掌管[l,r],mid=(l+r)/2,

左兒子掌管[l,mid] 右兒子掌管[mid+1,r]

每個點的資訊可由子節點更新,且都為二叉樹,所以我們用p*2來記錄左兒子,P*2+1來記錄右兒子

建樹

inline void build(int i,int l,int r){//遞迴建樹
    tree[i].l=l;tree[i].r=r;
    if(l==r){//如果這個節點是葉子節點
        tree[i].sum=input[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(i*2,l,mid);//分別構造左子樹和右子樹
    build(i*2+1,mid+1,r);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//剛才我們發現的性質
	return ;
} 

查詢方法

1、如果這個區間被完全包括在目標區間裡面,直接返回這個區間的值
2、如果這個區間的左兒子和目標區間有交集,那麼搜尋左兒子
3、如果這個區間的右兒子和目標區間有交集,那麼搜尋右兒子

inline int search(int i,int l,int r){
    if(tree[i].l>=l && tree[i].r<=r)//如果這個區間被完全包括在目標區間裡面,直接返回這個區間的值
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  return 0;//如果這個區間和目標區間毫不相干,返回0
    int s=0;
    if(tree[i*2].r>=l)  s+=search(i*2,l,r);//如果這個區間的左兒子和目標區間又交集,那麼搜尋左兒子
    if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r);//如果這個區間的右兒子和目標區間又交集,那麼搜尋右兒子
    return s;
}

單點修改

inline void add(int i,int dis,int k){

    if(tree[i].l==tree[i].r){//如果是葉子節點,那麼說明找到了
        tree[i].sum+=k;
        return ;
    }
    if(dis<=tree[i*2].r)  add(i*2,dis,k);//在哪往哪跑
    else  add(i*2+1,dis,k);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//返回更新
    //一般如果需要求最大值或其他就改變這一句
    return ;
} 

區間修改,單點查詢

區間修改和單點查詢,我們的思路就變為:如果把這個區間加上k,相當於把這個區間塗上一個k的標記,然後單點查詢的時候,就從上跑道下,把沿路的標記加起來就好。

這裡面給區間貼標記的方式與上面的區間查詢類似,原則還是那三條,只不過第一條:如果這個區間被完全包括在目標區間裡面,直接返回這個區間的值變為了如果這個區間如果這個區間被完全包括在目標區間裡面,講這個區間標記k

inline void add(int i,int l,int r,int k){
    if(tree[i].l>=l && tree[i].r<=r){//如果這個區間被完全包括在目標區間裡面,講這個區間標記k
        tree[i].sum+=k;
        return ;
    }
    if(tree[i*2].r>=l)
        add(i*2,l,r,k);
    if(tree[i*2+1].l<=r)
        add(i*2+1,l,r,k);
}

void search(int i,int dis){
    ans+=tree[i].num;//一路加起來
    if(tree[i].l==tree[i].r)
        return ;
    if(dis<=tree[i*2].r)
        search(i*2,dis);
    if(dis>=tree[i*2+1].l)
        search(i*2+1,dis);
}

區間修改 區間查詢

的時候,我們按照如下原則:

1、如果當前區間被完全覆蓋在目標區間裡,講這個區間的sum+k*(tree[i].r-tree[i].l+1)

2、如果沒有完全覆蓋,則先下傳懶標記

3、如果這個區間的左兒子和目標區間有交集,那麼搜尋左兒子

4、如果這個區間的右兒子和目標區間有交集,那麼搜尋右兒子

void add(int i,int l,int r,int k)
{
    if(tree[i].r<=r && tree[i].l>=l)//如果當前區間被完全覆蓋在目標區間裡,講這個區間的sum+k*(tree[i].r-tree[i].l+1)
    {
        tree[i].sum+=k*(tree[i].r-tree[i].l+1);
        tree[i].lz+=k;//記錄lazytage
        return ;
    }
    push_down(i);//向下傳遞
    if(tree[i*2].r>=l)
        add(i*2,l,r,k);
    if(tree[i*2+1].l<=r)
        add(i*2+1,l,r,k);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
    return ;
}
void push_down(int i)
{
    if(tree[i].lz!=0)
    {
        tree[i*2].lz+=tree[i].lz;//左右兒子分別加上父親的lz
        tree[i*2+1].lz+=tree[i].lz;
        init mid=(tree[i].l+tree[i].r)/2;
        tree[i*2].data+=tree[i].lz*(mid-tree[i*2].l+1);//左右分別求和加起來
        tree[i*2+1].data+=tree[i].lz*(tree[i*2+1].r-mid);
        tree[i].lz=0;//父親lz歸零
    }
    return ;
}
void push_down(int i)
{
    if(tree[i].lz!=0)
    {
        tree[i*2].lz+=tree[i].lz;//左右兒子分別加上父親的lz
        tree[i*2+1].lz+=tree[i].lz;
        init mid=(tree[i].l+tree[i].r)/2;
        tree[i*2].data+=tree[i].lz*(mid-tree[i*2].l+1);//左右分別求和加起來
        tree[i*2+1].data+=tree[i].lz*(tree[i*2+1].r-mid);
        tree[i].lz=0;//父親lz歸零
    }
    return ;
}