樹狀數組應用總結

前言

做個總結,忘記之後再翻翻

首先明確一下樹狀數組的結構性質:

1.每個內部節點c[x]c[x]保存以它爲根的子樹中所有葉節點的和

2.每個內部節點c[x]c[x]的子節點個數等於lowbit(x)lowbit(x)的位數,即位爲1的數量

例如:
x=9=23+21x=9=2^3+2^1,分爲[1,8],[9,9][1,8],[9,9]兩個區間,區間和分別爲c[8],c[9]c[8],c[9]
x=13=23+22+20x=13=2^3+2^2+2^0,分爲[1,8],[9,12],[13,13][1,8],[9,12],[13,13]三個區間,區間和分別爲c[8],c[12],c[13]c[8],c[12],c[13]

3.除樹根外,每個內部節點c[x]c[x]的父節點是c[x+lowbit(x)]c[x+lowbit(x)]

4.樹的深度爲O(logN)O(logN)。因此可以保證在O(logN)O(logN)的時間內查詢前綴和

在這裏插入圖片描述



樹狀數組應用總結

主要分爲三種:

  • 單點修改,單點查詢(最基礎)
  • 區間修改,單點查詢
  • 區間修改,區間查詢

應用一:單點修改,單點查詢

最基礎的定義

查詢
int ask(int x)
{
    int ans=0;
    for (;x;x-=x&-x)ans+=c[x];
    return ans;
}
修改
void add(int x,int y)
{
    for (;x<=n;x+=x&-x)c[x]+=y;
}

例題:POJ2182
題意:有nn頭奶牛,身高1n1-n不等,站成一列,已知第ii頭奶牛前面有AiA_i頭比他矮,求出所有身高。
思路:從後往前思考。AnA_n表示前n1n-1頭牛有AnA_n頭比最後一頭矮,由於不存在重複身高,最後一頭牛的身高肯定是排第An+1A_n+1位的,同理第ii頭的身高一定是排第Ai+1A_i+1位,但是已經出現過的身高就得排除。可以通過維護一個初始全1的01序列,查詢第kk個1的位置並且將其修改爲0。用二分+樹狀數組即可
代碼

int n,A[maxn],c[maxn],ans[maxn];
int ask(int x){int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for(;x<=n;x+=x&-x)c[x]+=y;}
int solve(int x)
{
    int l=1,r=n;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if (ask(mid)<x)l=mid+1;
        else r=mid;
    }
    return l;
}
int main()
{
    scanf("%d",&n);
    A[1]=0;rep(i,2,n)scanf("%d",&A[i]);
    rep(i,1,n)add(i,1);
    for (int i=n;i>=1;i--)
    {
        ans[i]=solve(A[i]+1);
        add(ans[i],-1);
    }
    rep(i,1,n)W(ans[i]);
}


應用二:區間修改,單點查詢

新建數組bb,前綴和b[1b[1~x]x]反映了指令"C l r d"對a[x]a[x]的影響
1.把b[l]+db[l]+d
2.把b[r+1]db[r+1]-d
觀察一下數組bb的變化:
1.當1x<l1\leq x< l時:前綴和b[1b[1~x]x]無變化
2.當lxrl \leq x \leq r時:前綴和b[1b[1~x]x]增加了dd,因爲b[l]b[l]增加了dd
3.當r<xnr< x \leq n時:前綴和b[1b[1~x]x],因爲一加一減剛好抵消

也就是說 對於區間[l,r][l,r]內的數的影響,都體現在了數組bb的前綴和中
而求前綴和就是樹狀數組的基礎操作,累加上原先的a[x]a[x]即可得到"Q x"的答案

藍書例題:
代碼

int n,m,l,r,x,a[maxn],c[maxn];
char s[5];
int ask(int x){int ans=0;for (;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for (;x<=n;x+=x&-x)c[x]+=y;}
int main()
{
    scanf("%d%d",&n,&m);
    rep(i,1,n)scanf("%d",&a[i]);
    while(m--)
    {
        scanf("%s",s);
        if (s[0]=='Q')
        {
            scanf("%d",&x);
            W(ask(x)+a[x]);
        }
        else if (s[0]=='C')
        {
            scanf("%d%d%d",&l,&r,&x);
            add(l,x);
            add(r+1,-x);
        }
    }
}


應用三:區間修改,區間查詢

首先仍是維護一個數組bba[x]a[x]變化量就是前綴和b[1b[1~x]x]
那麼原數組aa的前綴和a[1a[1~x]x]變化量就是:
i=1xj=1ib[j]=(x+1)i=1xb[i]i=1x(i×b[i])\sum_{i=1}^x\sum_{j=1}^i b[j]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x (i×b[i])
第一部分就是維護bb的前綴和
第二部分就是維護i×b[i]i×b[i]的前綴和,令i×b[i]=c2[x]i×b[i]=c2[x]
由於只是乘了個ii,故與之前對應的操作是:
1.把c2[l]+ldc2[l]+ld
2.把c2[r+1](r+1)dc2[r+1]-(r+1)d
觀察一下數組bb的變化:
lxrl \leq x \leq r時:前綴和c2[1c2[1~x]x]增加了ldld,因爲c2[l]c2[l]增加了ldld

綜上所述:aa的原始前綴和+aa的前綴和變化量即爲最終的aa的前綴和
Sum[r]=sum[r]+(r+1)i=1rb[i]i=1r(i×b[i])Sum[r]=sum[r]+(r+1)\sum_{i=1}^r b[i]-\sum_{i=1}^r (i×b[i])
=sum[r]+(r+1)ask1(r)ask2(r)=sum[r]+(r+1)ask1(r)-ask2(r)

區間查詢即輸出Sum[r]Sum[l1]Sum[r]-Sum[l-1]

藍書例題:
代碼

int n,m;
ll a[maxn],c1[maxn],c2[maxn],sum[maxn],l,r,x;
char s[5];
ll ask1(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c1[x];return ans;}
ll ask2(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c2[x];return ans;}
void add1(ll x,ll y){for(;x<=n;x+=x&-x)c1[x]+=y;}
void add2(ll x,ll y){for(;x<=n;x+=x&-x)c2[x]+=y;}
int main()
{
    scanf("%d%d",&n,&m);
    rep(i,1,n)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
    while(m--)
    {
        scanf("%s",s);
        scanf("%lld%lld",&l,&r);
        if (s[0]=='Q')
        {
            ll ans1=sum[r]-sum[l-1];
            ll ans2=(r+1)*ask1(r)-l*ask1(l-1);
            ll ans3=-ask2(r)+ask2(l-1);
            WW(ans1+ans2+ans3);
        }
        else
        {
            scanf("%lld",&x);
            add1(l,x);
            add1(r+1,-x);
            add2(l,l*x);
            add2(r+1,-(r+1)*x);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章