線段樹·題解報告

                                                  線段樹·題解報告

參考資料

·課件

線段樹 --劉汝佳 

統計的力量,線段樹全接觸 --張昆瑋

·Blog

【完全版】線段樹

從普通線段樹到zkw線段樹

[總結][數據結構]ZKW線段樹詳解 

 

選題目錄

· Hdu1166 敵兵佈陣(單點更新,區間求和) 

· Hdu1754 I Hate It(單點更新,RMQ)

· Hdu3308 LCIS(單點更新,區間並) 

· Poj3468 A Simple Problem with Integers(區間加減,區間求和)

· Poj2777 Count Color(區間修改,區間查詢,染色) 

 

線段樹總結

普通版遞歸線段樹:


  

每層都是[a,b]的劃分L=b-a, 則共log2L

任兩個結點要麼是包含關係要麼沒有公共部分不可能部分重疊

給定一個葉子p, 從根到p路徑上所有結點(p的所有直系祖先)代表的區間都包含點p, 且其他結點代表的區間都不包含點p

給定一個區間[l, r], 可以把它分解爲不超過2log2L條不相交線段的並

Lazy思想記錄有哪些指令而不真正執行它們等到需要計算的時候再說

根據題目要求確定維護信息和附加信息

具有解決問題的通用性

 

 

II zkw線段樹:

 

堆式存儲,好寫,效率較高

自底向上,非遞歸更新和查詢

Lazy標記

 

 

題解報告              

                      題目1:Hdu1166 敵兵佈陣

 

題目鏈接

http://acm.hdu.edu.cn/showproblem.php?pid=1166

 

題目大意

有N個正整數,對其進行三種操作:

Add   i, j                    第i個數增加j

Sub   i, j    第i個數減去j

Query i, j                    查詢區間[i, j]中數的和

Input

第一行一個整數T,表示有T組數據。

每組數據第一行一個正整數N(N<=50000),接下來有N個整數。

接下來每行有一條命令,命令有4種形式:

Add   i, j                    第i個數增加j

Sub   i, j    第i個數減去j

Query i, j                    查詢區間[i, j]中數的和

End   表示結束

每組數據最多有40000條命令

Output

對第i組數據, 首先輸出Case i:和回車,

對於每個Query詢問,輸出一個整數並回車,表示詢問的區間中數的總和,這個數不超過1000000.

 

思路

線段樹的單點修改,區間求和,可以用zkw線段樹維護區間和,提高效率,並便於編寫代碼.

 

算法步驟

1. 建樹

存儲空間: T[4 * N], 初始化爲0

M:                        M = 1; while (M < n + 2) M <<= 1;

T[M+1]T[M+n]:          n個輸入的整數

T[M-1]T[1]:    T[i] = T[2*i] + T[2*i+1];  

2. 更新x

更新最下層的數T[x+M], 並自底向上更新其父結點的值

T[i] = T[2*i] + T[2*i+1]; 

  3. 查詢[l,r]

令閉區間[l,r]變成(l-1,r+1).

自底向上查詢(l = l-1+M, r = r+1+m)開始查詢如果l

是樹中左結點(~l & 1)則加上其右端點值;

如果r是樹中右結點(r & 1)則加上其左端點值;

向上查詢: l >>= 1; r >>= 1;

lr是同層兄弟時結束(l ^ r ^ 1)

 

算法複雜度

建樹O(n), 查詢和修改O(logn)

總時間複雜度: O(Alogn)                     (A爲總操作數)

總空間複雜度: O(4 * n)

 

源程序

/*

 * Author:    HongSheng Zeng

 * Email:     [email protected]

 * FileName:  1166.cpp

 * Creation:  2014/08/31

 */

#include <iostream>

#include <cstdio>

#include <string>

#include <string.h>

using namespace std;

 

const int N = 50000 + 10;

int n, M, i;

int T[4 * N];

 

void BuildTree()

{

        memset(T, 0, sizeof(T));

        M = 1;

        while (M < n + 2)

                M <<= 1;

        for (i = M + 1; i <= M + n; ++i) 

                scanf("%d", &T[i]);

        for (i = M - 1; i; --i)

                T[i] = T[2 * i] + T[2 * i + 1];

}

 

int Query(int l, int r)

{

        l += M - 1; r += M + 1;

        int ans = 0;

        while (l ^ r ^ 1) {

                if (~l & 1) ans += T[l + 1];

                if (r & 1) ans += T[r - 1];

                l >>= 1; r >>= 1;

        }

        return ans;

}

 

void update(int x, int k)

{

        x += M;

        T[x] += k;

        while (x > 1) {

                x >>= 1;

                T[x] = T[2 * x] + T[2 * x + 1];

        }

}

 

int main()

{

        int t, l, r, x, value;

        char ch[10];

        scanf("%d", &t);

        for (int i = 1; i <= t; ++i) {

                scanf("%d", &n);

                BuildTree();

                printf("Case %d:\n", i);

                while (scanf("%s", ch)) {

                        string s = string(ch);

                        if (s == "End")

                                break;

                        if (s == "Query") {

                                scanf("%d%d", &l, &r);

                                printf("%d\n", Query(l, r));

                        }

                        if (s == "Add") {

                                scanf("%d%d", &x, &value);

                                update(x, value);

                        }

                        if (s == "Sub") {

                                scanf("%d%d", &x, &value);

                                update(x, -value);

                        }

                }

        }

}

 

評測系統上運行結果

Accepted. 運行時間 312ms, 佔用內存1128KB.

 

 

 

 

                      題目2:Hdu1754 I Hate It 

 

題目鏈接

http://acm.hdu.edu.cn/showproblem.php?pid=1754

 

題目大意

老師需要詢問從某某到某某當中,分數最高的是多少,也需要更新某位同學的成績.

Input

題目包含多組測試,處理到文件結束。 

在每個測試的第一行,有兩個正整數 N  M ( 0<N<=200000,0<M<5000 ),分別代表學生的數目和操作的數目。 

學生ID編號分別從1編到N。 

第二行包含N個整數,代表這N個學生的初始成績,其中第i個數代表ID爲i的學生的成績。 

接下來有M行。每一行有一個字符 C (只取'Q'或'U') ,和兩個正整數A,B。 

當C爲'Q'的時候,表示這是一條詢問操作,它詢問ID從A到B(包括A,B)的學生當中,成績最高的是多少。 

當C爲'U'的時候,表示這是一條更新操作,要求把ID爲A的學生的成績更改爲B。 

Output

對於每一次詢問操作,在一行裏面輸出最高成績。

 

思路

線段樹的單點修改,區間求最值(RMQ),可以用zkw線段樹維護區間最值,提高效率,並便於編寫代碼.

 

算法步驟

 1.建樹

存儲空間: T[4 * N], 初始化爲0

M:                        M = 1; while (M < n + 2) M <<= 1;

T[M+1]T[M+n]:          n個輸入的整數

T[M-1]T[1]:    T[i] = max(T[2*i], T[2*i+1]);  

 2.更新x

更新最下層的數T[x+M], 並自底向上更新其父結點的值

T[i] = max(T[2*i], T[2*i+1]);

 3. 查詢[l,r]

令閉區間[l,r]變成(l-1,r+1).

自底向上查詢(l = l-1+M, r = r+1+m)開始查詢.

Int ans = 0;

如果l是樹中左結點(~l & 1)且其右端點值大於ans,ans等於其右端點值;

如果r是樹中右結點(r & 1)且其左端點值大於ans,ans等於其左端點值;

向上查詢:  l >>= 1; r >>= 1;

lr是同層兄弟時結束(l ^ r ^ 1)

 

算法複雜度

建樹O(n), 查詢和修改O(logn)

總時間複雜度: O(Alogn)                     (A爲總操作數)

總空間複雜度: O(4 * n)

 

源程序

/*

 * Author:    HongSheng Zeng

 * Email:     [email protected]

 * FileName:  1754.cpp

 * Creation:  2014/09/01

 */

#include <iostream>

#include <cstdio>

#include <string.h>

using namespace std;

 

const int N = 200000 + 10;

int n, m, M, l, r, x, k;

int T[4 * N];

 

void Build_Tree()

{

        memset(T, 0, sizeof(T));

        M = 1;

        while (M < n + 2) 

                M <<= 1;

        for (int i = M + 1; i <= M + n; ++i)

                scanf("%d", &T[i]);

        for (int i = M - 1; i; --i)

                T[i] = max(T[i * 2], T[i * 2 + 1]);

}

 

void Update()

{

        x += M;

        T[x] = k;

        while (x) {

                x >>= 1;

                T[x] = max(T[x * 2], T[x * 2 + 1]);

        }

}

 

int Query()

{

        l += M - 1; r += M + 1;

        int ans = 0;

        while (l ^ r ^ 1) {

                if (~l & 1) 

                        ans = max(ans, T[l + 1]);

                if (r & 1)

                        ans = max(ans, T[r - 1]);

                l >>= 1; r >>= 1;

        }

        return ans;

}

 

int main()

{

        while (scanf("%d%d", &n, &m) != EOF) {

                Build_Tree();

                char ch;

                while (m--) {

                        scanf(" %c", &ch);

                        if (ch == 'Q') {

                                scanf("%d%d", &l, &r);

                                printf("%d\n", Query());

                        }

                        else {

                                scanf("%d%d", &x, &k);

                                Update();

                        }

                }

        }

}

 

評測系統上運行結果

Accepted. 運行時間 953ms, 佔用內存3448KB.

 

 

 

 

 

                     題目3:Hdu3308 敵兵佈陣 

 

題目鏈接

http://acm.hdu.edu.cn/showproblem.php?pid=3308

 

題目大意

給出一個長度爲N(N <= 100000)的數列,然後是兩種操作:
U A B: 將第A個數替換爲B (下標從零開始)
Q A B: 輸出區間[A, B]的最長連續遞增子序列
詢問的次數m <= 100000。

Input

第一行一個整數T,表示有T組數據。

每組數據第一行有一個整數n和m(0<n,m<=105),接下來有n個整數(0<=val<=105);

接下來m行,每行有一條命令,命令有2種形式:

U A B    (用B替換第A個數, 下標從0開始計數)

Q A B   (查詢區間[A, B]中最長連續子序列的長度) 

Output

對於每個Q操作,輸出結果.

 

思路

線段樹的單點修改,求區間並(查詢區間的最長連續子序列)

Case1: 父節點的左兒子右端點值 >= 父節點的右兒子左端點值

父節點維護區間中的最長連續子序列=max(左兒子最長連續子序列,右兒子最長連續子序列)

 

Case2: 父節點的左兒子右端點值 父節點的右兒子左端點值 

父節點維護區間中的最長連續子序列=max(左兒子最長連續子序列,右兒子最長連續子序列,左兒子維護區間中以其右端點結束的最長連續子序列+右兒子維護區間中以其左端點開始的最長連續子序列)

故線段樹中需要維護區間的最長連續子序列(smax),區間以左端點開始的最長連續子序列(lmax),和區間以右端點結束的最長連續子序列(rmax).維護信息較複雜且查詢時自頂向下比較方便,故用普通版的遞歸線段樹.

算法步驟

0. 信息向上更新

通過Up操作(詳見源代碼)將父節點的左右兒子的smaxlmaxrmax信息更新到父節點。

1.建樹

struct node 

{

        int l, r, lmax, rmax, smax;

} tree[3 * N];  

從上往下建樹,然後從下往上更新維護信息.

2.更新x

從上往下,確定x,並更新其值,然後通過Up操作往上更新維護信息。 

3. 查詢[l,r]

從上往下遞歸查詢區間,跨區間時需合併結果。

 

算法複雜度

建樹O(n), 查詢和修改O(logn)

總時間複雜度: O(Alogn)                     (A爲總操作數)

總空間複雜度: O(3 * n)

源程序

/*

 * Author:    HongSheng Zeng

 * Email:     [email protected]

 * FileName:  3308.cpp

 * Creation:  2014/09/10

 */

#include <iostream>

#include <cstdio>

#include <string.h>

using namespace std;

 

const int  N = 100000 + 10;

int num[N];

 

struct node 

{

        int l, r, lmax, rmax, smax;

} tree[3 * N];

 

void Up(int x)

{

        int l = tree[x].l, r = tree[x].r;

        int mid = (l + r) / 2;

        tree[x].lmax = tree[x * 2].lmax;

        tree[x].rmax = tree[x * 2 + 1].rmax;

        tree[x].smax = max(tree[x * 2].smax, tree[x * 2 + 1].smax);

        if (num[mid] < num[mid + 1]) {

                if (tree[x].lmax == (mid - l + 1))

                        tree[x].lmax += tree[x * 2 + 1].lmax;

                if (tree[x].rmax == (r - mid))

                        tree[x].rmax += tree[x * 2].rmax;

                tree[x].smax = max(tree[x].smax, tree[x * 2].rmax + tree[x * 2 + 1].lmax);

        }

}

 

void BuildTree(int x, int l, int r) 

{

        tree[x].l = l;

        tree[x].r = r;

        if (l == r) {

                tree[x].lmax = tree[x].rmax = tree[x].smax = 1;

                return;

        }

        int mid = (l + r) / 2;

        BuildTree(x * 2, l, mid);

        BuildTree(x * 2 + 1, mid + 1, r);

        Up(x);

}

 

void Update(int x, int k)

{

        if (tree[x].l == tree[x].r) 

                return;

        int l = tree[x].l , r = tree[x].r;

        int mid = (l + r) / 2;

        if (k <= mid)

                Update(x * 2, k);

        else 

                Update(x * 2 + 1, k);

        Up(x);

}

 

int Query(int x, int l, int r) 

{

        if (tree[x].l == l && tree[x].r == r)

                return tree[x].smax;

        int mid = (tree[x].l + tree[x].r) / 2;

        if (r <= mid)

                return Query(x * 2, l, r);

        else if (l > mid)

                return Query(x * 2 + 1, l, r);

        else {

                int ans = max(Query(x * 2, l, mid), Query(x * 2 + 1, mid + 1, r));

                if (num[mid] < num[mid + 1]) {

                        ans = max(ans, min(tree[x * 2].rmax, mid - l +1) + 

                                        min(tree[x * 2 + 1].lmax, r - mid));

                }

                return ans;

        }

}

                

int main()

{

        int t, l, r, index, k, n, m;

        char ch;

        scanf("%d", &t);

        while (t--) {

                scanf("%d%d", &n, &m);

                for (int i = 1; i <= n; ++i)

                        scanf("%d", &num[i]);

                BuildTree(1, 1, n);

                while (m--) {

                        scanf(" %c", &ch);

                        if (ch == 'Q') {

                                scanf("%d%d", &l, &r);

                                ++l; ++r;

                                printf("%d\n", Query(1, l, r));

                        }

                        else {

                                scanf("%d%d", &index, &k);

                                ++index;

                                num[index] = k;

                                Update(1, index);

                        }

                }

        }

}

 

評測系統上運行結果

Accepted. 運行時間 484ms, 佔用內存5832KB.

 

 

 

 

           題目4:Poj3468 A Simple Problem with Integers 

 

題目鏈接

http://poj.org/problem?id=3468

 

題目大意

有N個正整數,對其進行兩種操作:

‘Q a b ’     詢問a~b這段數的和

‘C a b c’    把a~b這段數都加上c

Input

第一行兩個整數N,Q, 1 ≤ N,Q ≤ 100000.

接下來有N個整數:A1, A2, ... , AN,  -1000000000 ≤ Ai ≤ 1000000000.

接下來有Q行,每行有一條命令,命令有2種形式:

"C a b c"     Aa, Aa+1, ... , Ab 的值都加上c,  -10000 ≤ c ≤ 10000.
"Q a b"      查詢區間Aa, Aa+1, ... , Ab的值的總和.

Output

對於每個Query詢問,輸出一個數並回車,表示詢問的區間中數的總和.

思路

線段樹的區間加,區間求和,使用線段樹+lazy標記,因爲只需要維護區間和,從下往上也可以便利地查詢,所以用zkw線段樹。

算法步驟

1. 建樹

存儲空間: T[4 * N], 初始化爲0

標記空間: mark[4 * N], 初始化爲0, mark[i]表示i結點以下所有結點更新值之和,mark[i]不爲0 時說明結點i的更新信息還沒更新到其子結點.

M:                        M = 1; while (M < n + 2) M <<= 1;

h:                        樹的高度

T[M+1]T[M+n]:          n個輸入的整數

T[M-1]T[1]:    T[i] = T[2*i] + T[2*i+1];  

  2. down(x)操作

 標記下傳沿着根結點到x的線路,父結點的標記信息更新到其子結點,並清空父結點的標記    信息.

  3. up(x)操作

沿着x到根結點的路線,用子結點的維護信息更新父結點的維護信息.

  4.更新[l, r], +k

l += M - 1; r += M + 1;

標記下傳down(l), down(r);

lazy更新結點:

如果l是樹中左結點(~l & 1)則更新其右端點值+k,並更新其標記值+k;

如果r是樹中右結點(r & 1)則更新其左端點值+k,並更新其標記值+k;

向上更新: l >>= 1; r >>= 1;

lr是同層兄弟時結束(l ^ r ^ 1)

       對l父結點和r父結點進行up操作

  5. 查詢[l,r]

l += M - 1; r += M + 1;

標記下傳down(l), down(r)

查詢:

如果l是樹中左結點(~l & 1)則加上其右端點值;

如果r是樹中右結點(r & 1)則加上其左端點值;

向上查詢: l >>= 1; r >>= 1;

lr是同層兄弟時結束(l ^ r ^ 1)

 

算法複雜度

建樹O(n), 查詢和修改O(logn)

總時間複雜度: O(Alogn)                     (A爲總操作數)

總空間複雜度: O(4 * n)

源程序

/*

 * Author:    HongSheng Zeng

 * Email:     [email protected]

 * FileName:  3468.cpp

 * Creation:  2014/09/03

 */

#include <iostream>

#include <cstdio>

#include <string.h>

using namespace std;

 

const int N = 100000 + 10;

int n, q, M, h, l , r, ll, rr, t;

long long k;

long long T[4 * N];

long long mark[4 * N];

 

void Build_Tree()

{

        memset(T, 0, sizeof(T));

        memset(mark, 0, sizeof(mark));

 

        int i;

        M = 1; h = 0;

        while (M < n + 2) {

                M <<= 1;

                ++h;

        }

        for (i = M + 1; i <= M + n; ++i) 

                scanf("%lld", &T[i]);

        for (i = M - 1; i; --i)

                T[i] = T[i * 2] + T[2 * i + 1];

}

 

void down(int x)

{

        for (int i = h; i; --i) 

                if (mark[t = x >> i]) {

                        mark[t] >>= 1;

                        mark[t << 1] += mark[t]; mark[t * 2 + 1] += mark[t];

                        T[t << 1] += mark[t]; T[t * 2 + 1] += mark[t];

                        mark[t] = 0;

                }

}

 

void up(int x)

{

        while (x) {

                T[x] = T[x << 1] + T[x * 2 + 1];

                x >>= 1;

        }

}

 

void Update()

{

        l += M -1; r += M + 1;

        ll = l >> 1; rr = r >> 1;

        down(l); down(r);

        while (l ^ r ^ 1) {

                if (~l & 1) {

                        T[l + 1] += k;

                        mark[l + 1] += k;

                }

                if (r & 1) {

                        T[r - 1] += k;

                        mark[r - 1] += k;

                }

                k <<= 1;

                l >>= 1; r >>= 1;

        }

        up(ll); up(rr);

}

        

long long Query()

{

        long long ans = 0;

        l += M - 1; r += M + 1;

        down(l); down(r);

        while (l ^ r ^ 1) {

                if (~l & 1) ans += T[l + 1];

                if (r & 1) ans += T[r - 1];

                l >>= 1; r >>= 1;

        }

        return ans;

}

 

int main()

{

        scanf("%d%d", &n, &q);

        Build_Tree();

        char ch;

        for (int i = 0; i < q; ++i) {

                scanf(" %c%d%d", &ch, &l, &r);

                if (ch == 'Q') {

                        printf("%lld\n", Query());

                } else {

                        scanf("%lld", &k);

                        Update();

                }

        }

}

評測系統上運行結果

Accepted. 運行時間 2219ms, 佔用內存6952KB.

 

 

 

                            題目5:Poj2777 Count Color

 

題目鏈接

http://poj.org/problem?id=2777

 

題目大意

有一個區間[1,L],被分成L段,標記爲1,2...L;最多有T種顏色。有兩種操作,一種是對某一個區間段染上某一種顏色,一種是詢問該區間有多少種不同的顏色。整個區間剛開始爲1.

Input

第一行L (1 <= L <= 100000), T (1 <= T <= 30) 和 O (1 <= O <= 100000). 

接下來O行,每行有一條命令,命令有2種形式:

C A B C     (給A到B區間染上C色)

Q A B      (查詢區間[A, B]有多少種不同的顏色) 

Output

對於每個Q操作,輸出結果.

 

思路

線段樹的染色問題,區間修改,區間查詢.

結點維護信息: 0-非純色; 非0-純顏色當結點爲非純色時,才需要訪問其子結點查詢

自上向下查詢比較方便,用普通版遞歸線段樹.

 

算法步驟

1.建樹

struct node

{

        int l, r, color;

} tree[3 * N]; 

從上往下建樹,顏色初始化爲1.

2.更新[l, r]

區間顏色標記下放,從上往下lazy更新. 

3. 查詢[l,r]

從上往下遞歸查詢,當結點爲非純色時,才向下查詢其子結點

 

算法複雜度

建樹O(n), 查詢和修改O(logn)

總時間複雜度: O(Alogn)                     (A爲總操作數)

總空間複雜度: O(3 * n)

源程序

/*

 * Author:    HongSheng Zeng

 * Email:     [email protected]

 * FileName:  2777.cpp

 * Creation:  2014/09/07

 */

#include <iostream>

#include <cstdio>

#include <string.h>

using namespace std;

 

const int N = 100000 + 10;

 

struct node

{

        int l, r, color;

} tree[3 * N];

 

bool mark[35];

 

void Build_Tree(int x, int l, int r)

{

        tree[x].l = l;

        tree[x].r = r;

        tree[x].color = 1;

        if (l == r)

                return;

        int mid = (l + r) / 2;

        Build_Tree(x * 2, l, mid);

        Build_Tree(x * 2 + 1, mid + 1, r);

}

 

void Update(int x, int l, int r, int color)

{

        if (tree[x].l == l && tree[x].r == r) {

                tree[x].color = color;

                return;

        }

 

        // Down 

        

        if (tree[x].color != 0 && tree[x].color != color) {

                tree[x * 2].color = tree[x].color;

                tree[x * 2 + 1].color = tree[x].color;

                tree[x].color = 0;

        }

 

        int mid = (tree[x].l + tree[x].r) / 2;

        if (r <= mid)

                Update(x * 2, l, r, color);

        else if (l > mid)

                Update(x * 2 + 1, l, r, color);

        else  {

                Update(x * 2, l, mid, color);

                Update(x * 2 + 1, mid + 1, r, color);

        }

}

 

void Query(int x, int l, int r)

        if (tree[x].color > 0) {

                mark[tree[x].color] = true;

                return;

        }

        int mid = (tree[x].l + tree[x].r) / 2;

        if (r <= mid)

                Query(x * 2, l, r);

        else if (l > mid)

                Query(x * 2 + 1, l, r);

        else {

                Query(x * 2, l, mid);

                Query(x * 2 + 1, mid + 1, r);

        }

}

int main()

{

        int L, T, O, l, r, k;

        char ch;

        scanf("%d%d%d", &L, &T, &O);

        memset(mark, false, sizeof(mark));

        Build_Tree(1, 1, L);

        while (O--) {

              scanf(" %c%d%d", &ch, &l, &r);

              if (l > r) {

                      l = l ^ r;

                      r = l ^ r;

                      l = l ^ r;

              }

              if (ch == 'C') {

                      scanf("%d", &k);

                      Update(1, l, r, k);

              }

              else {

                       memset(mark, false, sizeof(mark));

                       Query(1, l, r);

                       int sum = 0;

                       for (int i = 1; i <= T; ++i)

                               if (mark[i])

                                       ++sum;

                       printf("%d\n", sum);

              }

        }

}

 

評測系統上運行結果

Accepted. 運行時間 422ms, 佔用內存3764KB.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章