1. 程式人生 > >筆記—樹狀陣列&&線段樹

筆記—樹狀陣列&&線段樹

那是一個春光明媚的下午

我們被鎖在機房裡,被逼學樹狀陣列

邪惡的老師

學懂了才能走!

老師你看我核善的微笑
22

然後於痛苦和絕望中

我們學會了樹狀陣列&&線段樹(只是一點很膚淺的)

然後,

一道ban題

文章目錄

現在談談樹狀陣列

學過的都知道

也很輕鬆就能推出
單點修改-區間查詢
區間修改-單點查詢
區間修改-區間查詢,我就推不出來了orz(qwq

先說說

單點修改-區間查詢
而樹狀陣列程式碼大概長這樣
恰好有板題一道 ⇒敵兵佈陣
還有一道⇒更ban的題

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
const int M = 50001;
 
int n,tot,Bit[M];
 
inline int lowbit(int x)
{
    return x &
-x; } inline void update(int Index,int delta) { int i; for(i = Index;i <= n;i += lowbit(i)) Bit[i] += delta; } inline int sum(const int Index) { int i,Sum = 0; for(i = Index;i >0;i -= lowbit(i)) Sum += Bit[i]; return Sum; } int main() { int t; scanf
("%d",&t); while(t--) { tot++; printf("Case %d:\n",tot); int i; scanf("%d",&n); for(i = 1;i <= n;i++) { int soldier; scanf("%d",&soldier); update(i,soldier); } char s[15]; while(1) { scanf("%s",s); if(s[0] == 'E') break; int u,v; scanf("%d %d",&u,&v); if(s[0] == 'A') update(u,v); if(s[0] == 'S') update(u,-v); if(s[0] == 'Q') printf("%d\n",sum(v) - sum(u - 1)); } memset(Bit,0,sizeof(Bit)); } return 0; }

再談談

區間修改-單點查詢
ban題
其實
: 它和單點修改-區間查詢很像
就這樣搞搞
一個序列
↓                ↓
1 2 3 4 5 6 7 8 9
要使1 - 7區間內加上5

update(1,5);
update(8,-5);

應對查詢就

sum(Index);

ok

⇝ ⇝ ⇝ ⇝就有經驗

區間修改-區間查詢

蒟蒻的我

再也推不出來了

看下錶

6:00

(⊙o⊙)…
看來是走不了了

不行,我的fate/stay night[unlimited blade works]還沒看完
我的saber
我的手辦
祈的歌我還沒有聽夠

我怎麼會就這樣隕落
這時,一聲神祕的voice出現
少年,學線段樹吧

於是就有了…

首先建樹

inline void tree_built(int l,int r,int k)
{
    if(l == r)
    {
        tree[k] = num[l];
        return;
    }
    int mid = l + r >> 1;
    k = k << 1;
    //<<左移一位(2進位制),位運算,優先順序低於=-*/
    //>>同理
    tree_built(l,mid,k);
    tree_built(mid + 1,r,k ^ 1);
    tree[k >> 1] = tree[k] + tree[k ^ 1];
    //因為此時k的二進位制末位一定為0,0 ^ 1 = 0 + 1
}

設有xϵ{num1,…numn}
                  1 ~ 8
                 ↙     ↘
            1 ~ 4    5 ~ 8
           ↙ ↘           ↙ ↘
     1~2    3~4     5~6   7~8
1      2 3  4     5  6  7   8

可見每次修改一個區間o(log n),
查詢亦為o(log n)
那為什麼

編號為k的左子樹編號為k << 1 
右子樹編號為k << 1 ^ 1  ??
k = k << 1;
tree_built(l,mid,k);
tree_built(mid + 1,r,k ^ 1);

證明:
第q(q >= 2,前兩層手推)層的從左往右第a個區間
編號為 20 + 21 + 22 + ··· +2q - 2 + a
現在…
這個區間的左兒子的編號          這個區間的編號×2
⇔(20 + 21 + 22 + ··· +2q - 2 + a)* 2 = 20 + 21 + 22 + ··· +2q - 1 + (a - 1) * 2 + 1
⇔ 21 + 22 + 23 + ··· +2q - 1 + 2 * a = 1 + 21 + 22 + ··· +2q - 1 + (a - 1) * 2 + 1
⇔ 2 + 22 + 23 + ··· +2q - 1 + 2 * a = 2 + 21 + 22 + ··· +2q - 1 + 2 * a - 2
⇔ 22 + 23 + ··· +2q - 1 + 2 * a = 22 + ··· +2q - 1 + 2 * a
⇔ 2 * a = 2 * a
得證.
然後時間複雜度大約為o(樹的結點數)

然後求和

int query(int l,int r,int k)
{
    if(r <= Right&&l >= Left)
        return tree[k];
    int mid = l + r >> 1;
    int k = k << 1;
    int sum = 0;
    if(Left <= mid)
        sum = modify(l,mid,k);
    if(Right > mid)
        sum += modify(mid + 1,r,k ^ 1);
    return sum;
}

清晰易懂,不用解釋

修改

inline void modify(int k,int l,int r,int delta)
{
    if(Left <= l&&Right >= r)
    {
        Add(k,l,r,delta);
        return;
    }
    int mid = l + r >> 1;
    PushDown(k,l,r,mid);
    //如果,要往下,先將這裡的修改值傳下去
    if(Left <= mid)
        modify(k << 1,l,mid,delta);
    if(mid < Right)
        modify(k << 1 ^ 1,mid + 1,r,delta);
    tree[k] = tree[k << 1] + tree[k << 1 ^ 1];
}

上面就有兩個函式
Add&PushDown

這裡用的是標記下傳的方法

inline void Add(int k,int l,int r,int delta)
{
    add[k] += delta;
    tree[k] += ll(delta) * (r - l + 1);
}
inline void PushDown(int k,int l,int r,int mid)
{
    if(add[k] == 0)
        return;
    Add(k << 1,l,mid,add[k]);
    Add(k << 1 ^ 1,mid + 1,r,add[k]);
    add[k] = 0;
}

求和

inline ll query(int k,int l,int r)
{
    if(r <= Right&&l >= Left)
        return tree[k];
    int mid = l + r >> 1;
    PushDown(k,l,r,mid);
    //唯一值得注意的就是這裡也需要下傳
    //因為上一次可能修改了1~n,只有tree[1]被修改了,而查詢的卻是1~1
    ll ans = 0;
    if(mid >= Left)
        ans = query(k << 1,l,mid);
    if(Right > mid)
        ans += query(k << 1 ^ 1,mid + 1,r);
    return ans;
}

合起來

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 100000;
ll tree[MAXN * 4];
int num[MAXN + 1],add[MAXN * 4],Left,Right;
inline void Read(int *x)
{
    *x = 0;
    char a = getchar();
    bool f = 0;
    while(a > '9'||a < '0') {if(a == '-') f = 1;a = getchar();}
    while(a <= '9'&&a >= '0') {*x = *x * 10 + a - '0';a = getchar();}
    if(f)
        *x *= -1;
}
inline void tree_built(int l,int r,int k)
{
    if(l == r)
    {
        tree[k] = num[l];
        return;
    }
    int mid = l + r >> 1;
    k = k << 1;
    tree_built(l,mid,k);
    tree_built(mid + 1,r,k ^ 1);
    tree[k >> 1] = tree[k] + tree[k ^ 1];
}
inline void Add(int k,int l,int r,int delta)
{
    add[k] += delta;
    tree[k] += ll(delta) * (r - l + 1);
}
inline void PushDown(int k,int l,int r,int mid)
{
    if(add[k] == 0)
        return;
    Add(k << 1,l,mid,add[k]);
    Add(k << 1 ^ 1,mid + 1,r,add[k]);
    add[k] = 0;
}
inline ll query(int k,int l,int r)
{
    if(r <= Right&&l >= Left)
        return tree[k];
    int mid = l + r >> 1;
    PushDown(k,l,r,mid);
    ll ans = 0;
    if(mid >= Left)
        ans = query(k << 1,l,mid);
    if(Right > mid)
        ans += query(k << 1 ^ 1,mid + 1,r);
    return ans;
}
inline void modify(int k,int l,int r,int delta)
{
    if(Left <= l&&Right >= r)
    {
        Add(k,l,r,delta);
        return;
    }
    int mid = l + r >> 1;
    PushDown(k,l,r,mid);
    if(Left <= mid)
        modify(k << 1,l,mid,delta);
    if(mid < Right)
        modify(k << 1 ^ 1,mid + 1,r,delta);
    tree[k] = tree[k << 1] + tree[k << 1 ^ 1];
}
int main()
{
	int i,n,m;
	Read(&n),Read(&m);
	for(i = 1;i <= n;i++)
        scanf("%d",&num[i]);
	tree_built(1,n,1);
	while(m--)
    {
        int Case;
        Read(&Case),Read(&Left),Read(&Right);
        if(Case == 1)
        {
            int delta;
            Read(&delta);
            modify(1,1,n,delta);
        } else {
            ll ans = query(1,1,n);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

然後這道題就可以搞搞了

還有這道

以及這道