1. 程式人生 > >hdu 1166 敵兵布陣——(區間和)樹狀數組/線段樹

hdu 1166 敵兵布陣——(區間和)樹狀數組/線段樹

har stdio.h 二叉 chang .net pre 計算機 大小 sta


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


Input 第一行一個整數T。表示有T組數據。
每組數據第一行一個正整數N(N<=50000),表示敵人有N個工兵營地。接下來有N個正整數,第i個正整數ai代表第i個工兵營地裏開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令。命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地添加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地降低j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束。這條命令在每組數據最後出現;
每組數據最多有40000條命令
Output 對第i組數據,首先輸出“Case i:”和回車,
對於每一個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數保持在int以內。

Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End 
Sample Output
Case 1:
6
33
59


WAY one:

這就是一個區間求和問題,能夠用樹狀數組來做:

在此之前。須要了解一下按位與運算符——&;計算的規則是,僅當兩個數都為真。則結果為真。

比如 90&45==8——————由於 在二進制中 0101 1010 (90) &

0010 1101(45)== 0000 1000 (8)

附 1: 負數在計算機中的存儲方式:以補碼存放,即對負數的絕對值的二進制取反再加一。

比如 1001(9)—0110(取反) —0111(+1)所以 0111 (-9)

怎樣理解?-9能夠看成 0-(9)。依據小學知識,轉換成二進制後,

0000 0000 0000 0000 (0)

- 0000 0000 0000 1001 (9) 不夠位。則要向前借一。於是變成:

1 0000 0000 0000 0000 (0)

- 0000 0000 0000 1001 (9) == 1111 1111 1111 0111(-9)

為什麽能夠通過取反加一得到呢? 能夠把上述的 1 0000 0000 0000 0000 寫成 1111 1111 1111 1111 + 0000 0000 0000 0001 ,則 0-9 == 1111 1111 1111 1111 - 0000 0000 0000 1001 即取反過程 然後 + 0000 0000 0000 0001 即加一過程。


附 2:-x&x 的意義 。由上可知,此式得到的值是這個正數的二進制位的第一個1之後的部分。包含1所得的數必定是2^n, 此式對樹狀數組意義重大。 此外可高速求得某個數的二進制末尾0的個數。

code:樹狀數組

#include <stdio.h>
#include <string.h>
#define MAX 50005

int c[MAX];
int Lowbit(int t)
{
    return t&(-t);
}
int getSum(int n)
{
    int sum=0;
    while(n>0)
    {
        sum+=c[n];
        n-=Lowbit(n);
    }
    return sum;
}

void Change(int i,int v,int n)
{
    while(i<=n)
    {
        c[i]+=v;
        i+=Lowbit(i);
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int j=1;j<=t;j++)
    {
        memset(c,0,sizeof(c));
        printf("Case %d:\n",j);
        int n,a;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);
            Change(i,a,n);
        }
        char cmd[10];
        while(scanf("%s",cmd),cmd[0]!=‘E‘)
        {
            int p,q;
            if(cmd[0]==‘A‘)
            {
                scanf("%d%d",&p,&q);
                Change(p,q,n);
            }
            else if(cmd[0]==‘S‘)
            {
                scanf("%d%d",&p,&q);
                Change(p,-q,n);
            }
            else
            {
                scanf("%d%d",&p,&q);
                if(p!=1)printf("%d\n",getSum(q)-getSum(p-1));
                else printf("%d\n",getSum(q));
            }
        }
    }
    return 0;
}

附圖:

技術分享技術分享

技術分享技術分享技術分享技術分享


希望能夠通過上圖更好的理解求和的過程。

事實上質是一顆二叉索引樹


WHY two:

更高大上一點就是用線段樹去做了。

在此處我是用數組來模擬一個全然二叉樹,基本存儲原理是,假設用一維數組來存一個二叉樹。假設下標從一開始。父親節點乘2是左兒子節點,父親節點乘2加一是右兒子節點。兒子節點除以二是父親節點。事實上原理和上面的樹狀數組幾乎相同,僅僅是實現的方法不一樣而已。

如圖 :

code:

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<string>
#include<stack>
#include<queue>
#include<map>
#define Max(a, b) (a)>(b)?(a):(b)
#define inf 0x3f3f3f3f
#define lson l,m,rt<<1 //找到左兒子
#define rson m+1,r,rt<<1|1//找到右兒子
#define M 50008
using namespace std;

int segTree[M<<2];//大小為節點的四倍
//父親節點保存左右兒子節點的和
inline void pushrt(int rt)
{
    segTree[rt] = segTree[rt<<1] + segTree[rt<<1|1];
}
void build(int l, int r, int rt)
{
    if(l == r)
    {
        scanf("%d",&segTree[rt]);
        return ;
    }
    int m = (l + r)>>1;
    //遞歸建樹
    build(lson); 
    build(rson);
    pushrt(rt);
}
//單點更新
void update(int p, int add, int l, int r, int rt)
{
    if(l == r)
    {
        segTree[rt]+=add;
        return ;
    }
    int m=(l + r)>>1;
    //遞歸更新
    if(p <= m)
       update(p, add, lson);
    else
       update(p, add, rson);
    pushrt(rt);
}
int query(int L, int R, int l, int r, int rt)
{
    //假設l,r在所查詢的區間內,直接返回
    if(L <= l&&r<= R)
        return segTree[rt];
    int m=(l + r)>>1;
    int ans=0;
    //遞歸查詢
    if(L <= m)
        ans+=query(L, R, lson);
    if(R > m)
        ans+=query(L, R, rson);

    return ans;
}
int main()
{
    int n,t,p,q,o=1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        build(1,n,1);
        printf("Case %d:\n",o++);
        char op[10];
        while(scanf("%s",op)&&op[0]!=‘E‘)
        {
            scanf("%d%d",&p,&q);
            if(op[0]==‘Q‘)
                printf("%d\n",query(p,q,1,n,1));
            else if(op[0]==‘A‘)
                update(p,q,1,n,1);
            else
                update(p,-q,1,n,1);
        }
    }
    return 0;
}

這是屬於單點更新的線段樹。遞歸的地方比較難理解。debug一下,也許會理解得更快。另外就是為了執行速度更快的位運算,搞懂左移右移和或運算就ok了。


hdu 1166 敵兵布陣——(區間和)樹狀數組/線段樹