主頁 > 後端開發 > 樹狀陣列及應用

樹狀陣列及應用

2020-12-19 06:21:38 後端開發

1.樹狀陣列的原理

        在程式設計時,我們需要維護一個一維陣列A的前綴和S,設S[i]=A[1]+A[2]+…+A[i],

        如果我們修改了任意一個元素A[i]的值,則相關的前綴和S[i]、S[i+1]、…、S[n]都會發生變化,若采用傳統的線性順序掃描方法求連續陣列元素的和,則每次修改A[i]后,調整前綴和S的時間復雜度為O(n),若修改陣列A中元素的頻度為m,則調整前綴和S的時間復雜度為O(m*n),

        為提高維護前綴和的效率,可引入“樹狀陣列”,樹狀陣列通過將線性結構轉換成偽樹狀結構(線性結構只能逐個掃描元素,而樹狀結構可以實作跳躍式掃描),使得修改陣列元素值后,維護前綴和的時間復雜度為O(log2n),從而大大提高整體效率,

       設有陣列A[n],相應的樹狀陣列為C[n],當n=9時,其樹形結構如圖1所示,

圖1  樹狀陣列結構示意圖

        在圖1中,設黑色陣列元素A[1]~A[8]代表原來的陣列A,紅色陣列元素C[1]~C[8]就代表了樹狀陣列C,若圖中每個結點元素存放其子結點元素值的和,則有

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];

C[7] = A[7];

C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];

可以發現,樹狀陣列各元素的值是有規律的

C[i] = A[i - 2k+1] + A[i - 2k+2] + ... + A[i];   //  k為i的二進制中最右端1的位號

例如i =6時,其二進制數為110,最右端1的位置為1,故k =1,C[6]=A[6-2+1]+A[6];

i = 8時,其二進制數為1000,最右端1的位置為3,故k = 3,C[8]=A[8-8+1]+…+A[8],

(1)求前綴和,

        我們先來看如何利用樹狀陣列C,求A陣列中前i項的和,

例如,i=5時,

S[5]=A[1]+A[2]+A[3]+A[4]+A[5] ; 

由圖1知,C[4]=A[1]+A[2]+A[3]+A[4];   C[5]=A[5];

故:   S[5]=C[4]+C[5];

序號寫為二進制可得:S[(101)]=C[(100)]+C[(101)],

再如,i=7時,

S[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ; 

由圖1知,C[4]=A[1]+A[2]+A[3]+A[4];   C[6]=A[5]+A[6];   C[7]=A[7];

故:   S[7]=C[4]+C[6]+C[7];

序號寫為二進制可得:S[(111)]=C[(100)]+C[(110)]+C[(111)];

觀察上面的兩個示例,樹狀陣列其根本實質就是二進制的應用,

由此,可將求前綴和的操作寫成如下函式:

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

其中,函式lowbit(i)的回傳值為2k,k為i的二進制中最右端1的位號,

    下面以i=7為例模擬函式getsum(7)的執行程序,

    i=7(二進制數為111),res+=C[7]=C[7], lowbit(7)=001(二進制表示,對應1),i=7-1=6;

    i=6(二進制數為110),res+=C[6], lowbit(6)=010(二進制表示,對應2),i=6-2=4;

i=4(二進制數為100),res+=C[4], lowbit(4)=100(二進制表示,對應4),i=4-4=0;

i=0,結束回圈,回傳res(=C[7]+C[6]+C[4]),

       這樣,利用樹狀陣列能快速求任意區間[a,b]的和:A[a] + A[a+1] +…+ A[b],直接呼叫getsum(b)-getsum(a-1)即可,

(2)單點更新,

下面我們再討論樹狀陣列如何生成并維護,

當修改陣列A中的某一個元素A[i]的值時  應當如何更新陣列樹狀C呢?

        當修改A[i]的值時,可以從C[i]往根結點一路上溯,調整這條路線上的所有C[k]即可,

        例如,修改A[1]時,需要向上更新C[1] 、C[2]、C[4]、C[8],表示成二進制對應為:

         C[(001)]、C[(010)]、C[(100)]、C[(1000)]

        再如,修改A[5]時,需要向上更新C[5] 、C[6]、C[8],表示成二進制對應為:

       C[(101)]、C[(110)]、C[(1000)]

        觀察上面的兩個示例,單點更新其實與求前綴和互為逆運算,

        由此,可將單點更新的操作寫成如下函式:

void update(int i,int value)   // 修改A[i]值使其增加value后,更新樹狀陣列

{

    while (i<=n)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

        下面以修改A[5]為例,模擬函式update(5,val)的執行程序,

        i=5(二進制數為101),C[5]+=val,lowbit(5)=001(二進制表示,對應1),i=5+1=6;

        i=6(二進制數為110),C[6]+=val,lowbit(6)=010(二進制表示,對應2),i=6+2=8;

        i=8(二進制數為1000),C[8]+=val,lowbit(8)=10000(二進制表示,對應16),i=8+16=24;

        i=24>n,結束回圈,回圈期間,C[5]、C[6]、C[8]進行了更新,

      有了單點更新函式,就很容易根據給定陣列A構造對應的樹狀陣列C了,初始時,令陣列C的全部元素值為0,依次用陣列A的元素值A[1]~A[n],呼叫函式update(I,A[i]),即可構造出對應的樹狀陣列C,

     (3)lowbit函式的實作,

       函式lowbit(i)的回傳值為2k,k為i的二進制中最右端1的位號,這可以用一個運算式i&(-i)簡單地實作,即2^k = i&(-i) ,

       這里利用了負數的存盤特性,在計算機中負整數是以補碼存盤的,對于整數運算 x&(-x)有:

        1)當i為0時,即 0 & 0,結果為0;

        2)當i為奇數時,最后一個位元位為1,取反加1沒有進位,故x和-x除最后一位外前面的位正好相反(按位與結果為0),最后1位都為1,按位與后結果為1,

        3)當i為偶數,且為2的m次方時,x的二進制表示中只有一位是1(設為從右往左的第m+1位),其右邊有m位0,故x取反加1后,從右到左第有m個0,第m+1位及其左邊全是1,這樣,x& (-x) 得到的就是x,

       4)當i為偶數,卻不為2的m次方的形式時,可以寫作i= y * (2^k),其中,y的最低位為1,實際上就是把i用一個奇數左移k位來表示,這時,i的二進制表示最右邊有k個0,從右往左第k+1位為1,當對i取反時,最右邊的k位0變成1,第k+1位變為0;再加1,最右邊的k位就又變成了0,第k+1位因為進位的關系變成了1,左邊的位因為沒有進位,正好和i原來對應的位上的值相反,二者按位與,得到:第k+1位上為1,左邊右邊都為0,結果為2^k,

2.樹狀陣列的應用

        下面我們通過實體介紹樹狀陣列的應用,

2.1  單點更新、區間查詢

         對給定陣列的某個元素進行更新,然后對區間的和值進行查詢,

例1  【模板】樹狀陣列 1

        本題選自洛谷題庫(https://www.luogu.com.cn/problem/P3374)

題目描述

已知一個數列,你需要進行下面兩種操作:

將某一個數加上x

求出某區間每一個數的和

輸入格式

第一行包含兩個正整數 n,m,分別表示該數列數字的個數和操作的總個數,

第二行包含 n 個用空格分隔的整數,其中第 i 個數字表示數列第 i 項的初始值,

接下來 m 行每行包含3 個整數,表示一個操作,具體如下:

1 x k 含義:將第 x 個數加上 k

2 x y 含義:輸出區間 [x,y]內每個數的和

輸出格式

輸出包含若干行整數,即為所有操作 22 的結果,

輸入樣例

5 5

1 5 4 2 3

1 1 3

2 2 5

1 3 -1

1 4 2

2 1 4

輸出樣例

14

16

      (1)編程思路,

       典型的樹狀陣列應用模板題,直接撰寫并呼叫前面介紹的三個函式即可,

      (2)源程式,

#include <stdio.h>

#include <string.h>

int n;

int c[500001];

int lowbit(int x)

{

    return x&(-x);

}

void update(int i,int value)

{

    while (i<=n)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

int main()

{

    int m;

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

    memset(c, 0, sizeof c);

    int i,p,x,y;

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

    {

        scanf("%d",&x);

        update(i,x);

    }

    for (i=1;i<=m;i++)

    {

        scanf("%d%d%d",&p,&x,&y);

        if (p==1) update(x,y);

        else printf("%d\n",getsum(y)-getsum(x-1));

    }

    return 0;

}

2.2  區間更新、單點查詢

       設有陣列A[n],若要將區間[x,y]內的所有元素的值全部加上k或者減去k,然后查詢某個元素A[i]的值,這種時候該怎么做呢?

       如果直接采用陣列A的初始各元素值建立樹狀陣列C,然后將[x,y]區間內每個值都更新,最后利用getsum(i)-getsum(i-1)得到待查詢元素A[i]的值,這種辦法的復雜度肯定是不行的,最好的辦法是引入差分,利用差分建樹,

      設有陣列A[n+1],且令A[0]=0,利用公式 B[i]=A[i]-A[i-1]  (1≤i≤n) 建立差分陣列B,

      顯然有 A[i]=B[1]+B[2]+B[3]+ …+B[i]

                      =A[1]-A[0]+A[2]-A[1]+…+A[i]-A[i-1] =A[i]

       這樣,待查詢陣列元素A[i]的值就是差分陣列B的前i項的和,

       當陣列A的區間[x,y]內的所有元素(A[x]~A[y])的值全部加上k或者減去k時,區間內的元素的差值是不變的,因此對應B陣列只有B[x] 加上k或者減去k、B[y+1] 減去k或加上k,其余元素值不變,

        這樣,我們用差分陣列B構造樹狀陣列C,每次區間修改就只進行兩次單點修改, 

例2  【模板】樹狀陣列 2

          本題選自洛谷題庫(https://www.luogu.com.cn/problem/P3368)

題目描述

已知一個數列,你需要進行下面兩種操作:

將某區間每一個數加上x;

求出某一個數的值,

輸入格式

第一行包含兩個整數 N、M,分別表示該數列數字的個數和操作的總個數,

第二行包含 N 個用空格分隔的整數,其中第 i 個數字表示數列第 i 項的初始值,

接下來 M 行每行包含 2 或 4個整數,表示一個操作,具體如下:

操作 1: 格式:1 x y k 含義:將區間 [x,y]內每個數加上k;

操作 2: 格式:2 x 含義:輸出第 x 個數的值,

輸出格式

輸出包含若干行整數,即為所有操作 22 的結果,

輸入樣例

5 5

1 5 4 2 3

1 2 4 2

2 3

1 1 5 -1

1 3 5 7

2 4

輸出樣例

6

10

       (1)編程思路,

      典型的區間更新、單點查詢模板題,利用陣列相鄰元素的差值構造樹狀陣列,

      (2)源程式,

#include <stdio.h>

#include <string.h>

int n;

int a[500001],c[500001];

int lowbit(int x)

{

    return x&(-x);

}

void update(int i,int value)

{

    while (i<=n)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

int main()

{

    int m,i;

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

    memset(c, 0, sizeof c);

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

    {

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

        update(i,a[i] - a[i-1]);

    }

    for (i=1;i<=m;i++)

    {

        int f,x,y,k;

        scanf("%d",&f);

        if (f==1)

        {

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

            update(x,k);

            update(y+1,-k);

        }

        if (f==2)

        {

            scanf("%d",&x);

            printf("%d\n",getsum(x));

        }

    }

    return 0;

}

例3  Color the ball

       本題選自杭州電子科技大學OJ題庫(http://acm.hdu.edu.cn/showproblem.php?pid=1556),

Problem Description

N個氣球排成一排,從左到右依次編號為1,2,3....N,每次給定2個整數a b(a <= b),lele便為騎上他的“小飛鴿”牌電動車從氣球a開始到氣球b依次給每個氣球涂一次顏色,但是N次以后lele已經忘記了第I個氣球已經涂過幾次顏色了,你能幫他算出每個氣球被涂過幾次顏色嗎?

Input

每個測驗實體第一行為一個整數N,(N <= 100000).接下來的N行,每行包括2個整數a b(1 <= a <= b <= N),

當N = 0,輸入結束,

Output

每個測驗實體輸出一行,包括N個整數,第I個數代表第I個氣球總共被涂色的次數,

Sample Input

3

1 1

2 2

3 3

3

1 1

1 2

1 3

0

Sample Output

1 1 1

3 2 1

       (1)編程思路,

       設有陣列A[N+1],A[i]的值表示編號為i的氣球被涂色的次數,初始時元素值全為0,從氣球a開始到氣球b依次給每個氣球涂一次顏色相當于對陣列A的區間[a,b]中的每個元素加1,若定義陣列A的差值陣列B(差值陣列的初始值顯然也全為0),則對陣列A的區間[a,b]中的各元素進行加1修改操作,只需對應對陣列B的兩個元素B[a]和B[b+1]進行單點修改即可,其中B[a]加1,B[b+1]減1,

       根據樹狀陣列的原理,A[i]的值就是差值陣列B的前i項的和,呼叫函式getsum(i)即可,

       (2)源程式,

#include <stdio.h>

#include <string.h>

int n;

int c[100001];

int lowbit(int x)

{

    return x&(-x);

}

void update(int i,int value)

{

    while (i<=n)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

int main()

{

    while (scanf("%d",&n) && n!=0)

    {

        memset(c, 0, sizeof c);

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

        {

            int a,b;

            scanf("%d%d",&a,&b);

            update(a, 1);       

            update(b + 1, -1);    

        }

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

        {

            printf("%d ",getsum(i));

        }

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

    }

    return 0;

}

例4  Matrix

       本題選自北京大學OJ題庫(http://poj.org/problem?id=2155),

Description

Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row and j-th column. Initially we have A[i, j] = 0 (1 <= i, j <= N).

We can change the matrix in the following way. Given a rectangle whose upper-left corner is (x1, y1) and lower-right corner is (x2, y2), we change all the elements in the rectangle by using "not" operation (if it is a '0' then change it into '1' otherwise change it into '0'). To maintain the information of the matrix, you are asked to write a program to receive and execute two kinds of instructions.

1. C x1 y1 x2 y2 (1 <= x1 <= x2 <= n, 1 <= y1 <= y2 <= n) changes the matrix by using the rectangle whose upper-left corner is (x1, y1) and lower-right corner is (x2, y2).

2. Q x y (1 <= x, y <= n) querys A[x, y].

Input

The first line of the input is an integer X (X <= 10) representing the number of test cases. The following X blocks each represents a test case.

The first line of each block contains two numbers N and T (2 <= N <= 1000, 1 <= T <= 50000) representing the size of the matrix and the number of the instructions. The following T lines each represents an instruction having the format "Q x y" or "C x1 y1 x2 y2", which has been described above.

Output

For each querying output one line, which has an integer representing A[x, y].

There is a blank line between every two continuous test cases.

Sample Input

1

2 10

C 2 1 2 2

Q 2 2

C 2 1 2 1

Q 1 1

C 1 1 2 1

C 1 2 1 2

C 1 1 2 2

Q 1 1

C 1 1 2 1

Q 2 1

Sample Output

1

0

0

1

      (1)編程思路,

       本題的題意是給定N*N矩陣A,矩陣中的元素值非0即1,初始時元素值全為0,對矩陣中的子矩陣[x1, y1, x2, y2](x1、y1表示子矩陣左上角元素下標,x2、y2表示子矩陣右下角元素坐標)中的各元素進行變反操作(0變1,1變0),查詢矩陣中元素A[x][y]的值,

       根據題目中矩陣A的元素值不是 1 就是 0這一特點,我們只需記錄每個元素值改變過幾次,即可得到每個元素的值,

       由例3可知,當陣列是一維時,若要修改[x,y]區間的值,只需修改x和y+1這兩個點的值(將點x值加1,點y+1值減1),查詢k點的值時,呼叫函式getsum(k)即可,

       當陣列為二維時,解決方法類同一維,要修改范圍[x1, y1, x2, y2],只需修改這四個點:(x1,y1), (x1,y2+1), (x2+1,y1), (x2+1,y2+1),查詢點(x,y)的值時,呼叫函式getsum(x, y),

       按樹狀陣列的原理,需要將單點修改和求前綴和函式修改為二維的處理情況,

    (2)源程式,

#include <stdio.h>

#include <string.h>

int n;

int c[1001][1001];

int lowbit(int x)

{

    return x&(-x);

}

void update(int x, int y, int value)

{

    for (int i = x; i<= n; i+=lowbit(i))

       for (int j=y; j<= n; j+=lowbit(j))

          c[i][j]+=value;

}

int getsum(int x, int y)

{

    int res = 0;

    for (int i = x; i>0; i-=lowbit(i))

        for (int j = y; j > 0; j -= lowbit(j))

            res += c[i][j];

    return res;

}

int main()

{

    int t;

    scanf("%d",&t);

    while(t--)

    {

        int m,x1,y1,x2,y2;

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

        memset(c, 0, sizeof(c));

        char op[5];

        while (m--)

        {

            scanf("%s%d%d", op, &x1, &y1);

            if (op[0] == 'C')

            {

                scanf("%d%d", &x2, &y2);

                update(x1, y1, 1);

                update(x1, y2+1, 1);

                update(x2+1, y1, 1);

                update(x2+1, y2+1, 1);

            }

            else

            {

                printf("%d\n", getsum(x1,y1) %2);

            }

        }

        printf("\n");

    }

    return 0;

}

例5  Flowers

       本題選自杭州電子科技大學OJ題庫(http://acm.hdu.edu.cn/showproblem.php?pid=4325),

Problem Description

As is known to all, the blooming time and duration varies between different kinds of flowers. Now there is a garden planted full of flowers. The gardener wants to know how many flowers will bloom in the garden in a specific time. But there are too many flowers in the garden, so he wants you to help him.

Input

The first line contains a single integer t (1 <= t <= 10), the number of test cases.

For each case, the first line contains two integer N and M, where N (1 <= N <= 10^5) is the number of flowers, and M (1 <= M <= 10^5) is the query times.

In the next N lines, each line contains two integer Si and Ti (1 <= Si <= Ti <= 10^9), means i-th flower will be blooming at time [Si, Ti].

In the next M lines, each line contains an integer Ti, means the time of i-th query.

Output

For each case, output the case number as shown and then print M lines. Each line contains an integer, meaning the number of blooming flowers.

Sample outputs are available for more details.

Sample Input

2

1 1

5 10

4

2 3

1 4

4 8

1

4

6

Sample Output

Case #1:

0

Case #2:

1

2

1

       (1)編程思路1,

       本題的題意是:有n朵花和m個時間點,第i朵花正在開放的時間是si和ti,問在第mi個時間點有多少朵花正在開放?

       類同例3的編程思路,初始時所有時間點都沒有花兒開放,第i朵花正在開放的時間是si和ti,相當于對區間[si,ti]中每個元素加1(有一朵花開放),問在第mi個時間點有多少朵花正在開放,呼叫函式getsum(mi)得到結果,

       先定義樹狀陣列的大小為int c[100005];,仿例3撰寫如下的源程式,

      (2)可以Accepted但存在缺陷的源程式,

#include <stdio.h>

#include <string.h>

int n;

int c[100005];

int lowbit(int x)

{

    return x&(-x);

}

void update(int i,int value)

{

    while (i<=100005)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

int main()

{

    int t;

    scanf("%d",&t);

    for (int k=1;k<=t;k++)

    {

        memset(c, 0, sizeof c);

        int m;

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

        int i,s,t;

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

        {

            scanf("%d%d",&s,&t);

            update(s, 1);

            update(t + 1, -1);

        }

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

        for (i = 1; i<=m; i++)

        {

            scanf("%d",&t);

            printf("%d\n",getsum(t));

        }

    }

    return 0;

}

      這個源程式提交給OJ系統后,可以Accepted,但實際上這個程式是存在問題的,因為按題意花開的開始時間和結束時間Si 和Ti資料范圍為1 <= Si <= Ti <= 10^9,而程式中定義的陣列C,最大為100005,之所以Accepted,是因為測驗資料集太弱,與題目中資料說明存在偏差,如果測驗資料中輸入的Si或Ti大于10^5,程式運行會出錯,但將陣列C定義為int C[10e9+1]是不現實地,因為陣列C會過大,無法通過編譯,

      (3)編程思路2,

       由題意,輸入的Si 和Ti資料范圍為1 <= Si <= Ti <= 10^9,而花兒朵數N的范圍為1 <= N <= 10^5,每朵花開放有兩個時間點,查詢時間點個數M的范圍為1 <= N <= 10^5,因此可以想辦法將1~ 10^9范圍的資料縮小到1~3*10^5的范圍,為了將較大的資料范圍縮小到一個較小的資料范圍,可以進行離散化處理,

       離散化的處理方法是:將原始資料按從小到大順序排列,然后對資料編名次(相同的資料名次相同),然后各原始資料用其名次表示,

       以測驗樣例為例說明,

       例如,兩朵花的開放時間點分別為1、4和4、8,3個查詢時間點為1、4、6,這7個時間點按從小到大排序為1、1、4、4、6、8,相應名次為1、1、2、2、3、4,這樣離散化后,1用1表示,4用2表示,6用3表示,8用4表示,

       又如,3朵花的開放時間點分別為100、420,420、8900和123456789、987654321,4個查詢時間點為10、400、6000和87654,這10個時間點按從小到大排序為:

       10、100、400、420、420、6000、8900、87654、123456789、987654321,相應名次為1、2、3、4、4、5、6、7、8、9,這樣離散化后,10用1表示,100用2表示,400用3表示,420用4表示,…,987654321用5表示,

       離散化之后就可以采用樹狀陣列解決問題了,

      (4)正確進行離散化處理的源程式,

#include <stdio.h>

#include <string.h>

#include <algorithm>

using namespace std;

struct node

{

    int id,x;

};

struct node a[300005];

int c[300005],rankk[300005];

bool cmp(struct node a,struct node b)

{

    return a.x<b.x;

}

int lowbit(int x)

{

    return x&(-x);

}

void update(int i,int n,int value)

{

    while (i<=n)

    {

        c[i]+=value;

        i+=lowbit(i);

    }

}

int getsum(int i)

{

    int res = 0;

    while(i > 0)

    {

        res += c[i];

        i -= lowbit(i);

    }

    return res;

}

int main()

{

    int t;

    scanf("%d",&t);

    for (int k=1;k<=t;k++)

    {

        int n,m;

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

        memset(c,0,sizeof(c));

        memset(rankk,0,sizeof(rankk));

        int temp=2*n+m,i;

        for (i=1;i<=temp;i++)

        {

             scanf("%d",&a[i].x);

             a[i].id=i;

        }

        sort(a+1,a+1+temp,cmp);

        int cnt=1;

        rankk[a[1].id]=cnt;

        for (i=2;i<=temp;i++)

        {

            if(a[i].x==a[i-1].x)    // 判重

                rankk[a[i].id]=cnt;

            else

                rankk[a[i].id]=++cnt;

        }

        for (i=1;i<2*n;i+=2)

        {

             update(rankk[i],cnt,1);

             update(rankk[i+1]+1,cnt,-1);

        }

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

        for (i=2*n+1;i<=temp;i++)

        {

            printf("%d\n",getsum(rankk[i]));

        }

    }

    return 0;

}

2.3  區間更新、區間查詢

        由前面介紹的樹狀陣列原理可知,樹狀陣列的本質就是實作時間復雜度為O(log2n) 的單點更新,區間查詢”,將其進行變形,利用差值建樹狀陣列,可以實作“區間更新,單點查詢”,那么,如何實作“區間更新,區間查詢”呢?

       設有陣列a[n+1],且令a[0]=0,利用公式 c[i]=a[i]-a[i-1]  (1≤i≤n) 建立差分陣列c,

觀察式子:

a[1]+a[2]+...+a[n]

=(a[1]-0)+(a[1]-0+a[2]-a[1])+…+ (a[1]-0 +a[2]-a[1]+…+a[n]-a[n-1])

= (c[1]) + (c[1]+c[2]) + … + (c[1]+c[2]+ … +c[n]) 

= n*c[1] + (n-1)*c[2] +... +c[n]

= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])

    再建立一個樹狀陣列c2,其中c2[i] = (i-1)*c[i],由此,

a[1]+a[2]+...+a[n] = n* getsum (c,n) - getsum (c2,n)  

                   // 其中,getsum (c,n)表示樹狀陣列c的前n項的和

    而x到y的區間和即為:

(y* getsum (c,y) - getsum (c2,y))- ((x-1)* getsum (c,x-1) - getsum (c2,x-1))

例6  【模板】線段樹 1

    本題選自洛谷題庫(https://www.luogu.com.cn/problem/ P3372)

題目描述

已知一個數列,你需要進行下面兩種操作:

將某區間每一個數加上 k,

求出某區間每一個數的和,

輸入格式

第一行包含兩個整數 n, m,分別表示該數列數字的個數和操作的總個數,

第二行包含 n 個用空格分隔的整數,其中第 i 個數字表示數列第 i 項的初始值,

接下來m行每行包含3或4個整數,表示一個操作,具體如下:

1 x y k:將區間 [x, y]內每個數加上 k,

2 x y:輸出區間 [x,y] 內每個數的和,

輸出格式

輸出包含若干行整數,即為所有操作 2 的結果,

輸入樣例

5 5

1 5 4 2 3

2 2 4

1 2 3 2

2 3 4

1 1 5 1

2 1 4

輸出樣例

11

8

20

      (1)編程思路,

       典型的區間更新、區間查詢模板題,利用陣列相鄰元素的差值構造樹狀陣列c1,再利用樹狀陣列c1構造樹狀陣列c2,

      (2)源程式,

#include <stdio.h>

#include <string.h>

typedef long long LL;

#define MAXN 100001

LL n;

LL c1[MAXN],c2[MAXN];

LL lowbit(LL x)

{

       return x&(-x);

}

 

void update(LL x,LL value,LL *c)

{

       while(x<=n)

    {

              c[x]+=value;

              x+=lowbit(x);

       }

}

LL getsum(LL x,LL *c)

{

       LL sum=0;

       while(x>0)

    {

              sum+=c[x];

              x-=lowbit(x);

       }

       return sum;

}

int main()

{

       int q,op;

       LL x,y,d;

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

    memset(c1,0,sizeof(c1));

       memset(c2,0,sizeof(c2));

       y=0;

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

       {

           scanf("%lld",&x);

           d=x-y;

           update(i,d,c1);

           update(i,(i-1)*d,c2);

           y=x;

       }

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

    {

           scanf("%d",&op);

              if (op==1)

              {

                     scanf("%lld%lld%lld",&x,&y,&d);

                     update(x,d,c1);

                     update(y+1,-d,c1);

                     update(x,(x-1)*d,c2);

                     update(y+1,-y*d,c2);

              }

        else

        {

                     scanf("%lld%lld",&x,&y);

                     printf("%lld\n",getsum(y,c1)*y-getsum(x-1,c1)*(x-1) -getsum(y,c2)+getsum(x-1,c2));

              }

       }

       return 0;

}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/236880.html

標籤:C

上一篇:Vscode下載與配置(C語言)

下一篇:隔行如隔山!%c與%s,只有程式員才懂的C語言符號!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more