NYOJ 士兵殺敵 (樹狀數組)

來源:互聯網
上載者:User

首先看一道題:http://59.69.128.200/JudgeOnline/problem.php?pid=116

描述

南將軍手下有N個士兵,分別編號1到N,這些士兵的殺敵數都是已知的。

小工是南將軍手下的軍師,南將軍經常想知道第m號到第n號士兵的總殺敵數,請你協助小工來回答南將軍吧。

南將軍的某次詢問之後士兵i可能又殺敵q人,之後南將軍再詢問的時候,需要考慮到新增的殺敵數。

輸入
只有一組測試資料
第一行是兩個整數N,M,其中N表示士兵的個數(1<N<1000000),M表示指令的條數。(1<M<100000)
隨後的一行是N個整數,ai表示第i號士兵殺敵數目。(0<=ai<=100)
隨後的M行每行是一條指令,這條指令包含了一個字串和兩個整數,首先是一個字串,如果是字串QUERY則表示南將軍進行了查詢操作,後面的兩個整數m,n,表示查詢的起始與終止士兵編號;如果是字串ADD則後面跟的兩個整數I,A(1<=I<=N,1<=A<=100),表示第I個士兵新增殺敵數為A.
輸出
對於每次查詢,輸出一個整數R表示第m號士兵到第n號士兵的總殺敵數,每組輸出佔一行

範例輸入

5 6
1 2 3 4 5
QUERY 1 3
ADD 1 2
QUERY 1 3
ADD 2 3
QUERY 1 2
QUERY 1 5

範例輸出

6
8
8
20

問題提出:已知數組a[],元素個數為n,現在更改a中的元素,要求得新的a數組中i到j區間內的和(1<=i<=j<=n).

思考:對於這個問題,我們可以暴力地來解決,從a[i]一直累加到a[j],最壞的情況下複雜度為O(n),對於m次change&querry,合起來的複雜度為O(m*n),在n或m很大的情況下,這樣的複雜度是讓人無法忍受的.另外,如果沒有元素的變更,我們完全可以儲存sum[1,k](k=1,2,……),然後對任意給定的尋找區間[i,j],都可以方便的用ans=sum[1,j]-sum[1,i-1],當然這隻是沒有元素改變的情況下的比較最佳化的解法.那麼對於有元素變更的問題是否有更高效的方法呢?(廢話!沒有我還寫啥?!)可以想一下,每次更改的元素是比較少的,有時候甚至每次只改變一個元素,但是在用暴力方法求區間和的時候,卻對區間內所有的元素都累加了一遍,這樣其實造成了許多無謂的運算.這時候也許會想到如果能把一些結果存起來會不會減少很多運算?答案是肯定的,但問題是怎麼存,存什麼?如果存任意區間的話,n比較大的時候不但記憶體吃不消,而且儲存的量太大,不易更改,反而得不償失;那麼也許可以考慮儲存特定的一些區間(比如說線段樹,其實現在討論的問題用線段樹完全可以解,以後再詳細寫線段樹).那麼現在重新回過頭來,看下這個問題,我們已經確定了要儲存一些特定區間sum的想法,接下來我們要解決的無非是兩個問題:1、減少更改元素後對這些區間裡的sum值的更改時間.2、減少尋找的時間.

好了廢話了這麼半天,無非是想讓自己以及看到的人明白為什麼要用樹狀數組.

接下來正式入題.

首先我們可以借鑒元素不變更問題的最佳化方法,先得到前i-1項之和and前j項之和,以s[i]表示前i項之和,那麼sum[i,j]=s[j]-s[i-1].那麼現在的問題已經轉化為求前i項之和了.另外,我們已經確定要儲存一些特定區間的和,現在就要來揭示這些特定的區間究竟指什麼.

在文字說明之前先引入一個非常經典的,在網上找到的樹狀數組文章裡幾乎都要出現的一個圖片

不難發現,c[k]儲存的實際上是從k開始向前數k的二進位表示中右邊第一個1所代表的數字個元素的和(這麼說可能有點拗口,令lowbit為k的二進位表示中右邊第一個1所代表的數字,然後c[k]裡存的就是從a[k]開始向前數lowbit個元素之和)這麼存有什麼好處呢?無論是樹狀數組還是線段樹,都用到了分塊的思想,而樹狀數組採用這樣的儲存結構我想最主要的還是這樣方便計算,我們可以用位元運算輕鬆地算出lowbit.分析一下這樣做的複雜度:對於更改元素來說,如果第i個元素被修改了,因為我們最終還是要求和,所以可以直接在c數組裡面進行相應的更改,中的例子,假設更改的元素是a[2],那麼它影響到得c數組中的元素只有c[2],c[4],c[8],我們只需一層一層往上修改就可以了,這個過程的最壞的複雜度也不過O(logN);對於尋找來說,如尋找s[k],只需尋找k的二進位表示中1的個數次就能得到最終結果,比如尋找s[7],7的二進位表示中有3個1,也就是要尋找3次,到底是不是呢,我們來看,s[7]=c[7]+c[6]+c[4],可能你還不知道怎麼實現這個過程.

還以7為例,二進位為0111,右邊第一個1出現在第0位上,也就是說要從a[7]開始向前數1個元素(只有a[7]),即c[7];

然後將這個1舍掉,得到6,二進位表示為0110,右邊第一個1出現在第1位上,也就是說要從a[6]開始向前數2個元素(a[6],a[5]),即c[6];

然後舍掉用過的1,得到4,二進位表示為0100,右邊第一個1出現在第2位上,也就是說要從a[4]開始向前數4個元素(a[4],a[3],a[2],a[1]),即c[4].

代碼實現:

int lowbit(int x)//計算lowbit
{
    return x&(-x);
}
void add(int i,int val)//將第i個元素更改為val
{
    while(i<=n)
   {
         c[i]+=val;
         i+=lowbit(i);
     }
}
int sum(int i)//求前i項和
{
    int s=0;
    while(i>0)
    {
         s+=c[i];
         i-=lowbit(i);
     }
    return s;
}

#include <iostream>#include<cstdio>#include<string.h>#include<vector>using namespace std;int N,M;int a[1000005];int c[1000005]={0};int lowbit(int x){return x&(-x);}int sum(int i){int s=0;while(i>0){s+=c[i];i-=lowbit(i);}return s;}void change(int x,int m)//士兵增加    {  for(int i=x;i<=N;i+=lowbit(i))  c[i]+=m;  }  int main(){//freopen("1.txt","r",stdin);char c[10];scanf("%d%d",&N,&M);for(int i=1;i<=N;i++){scanf("%d",&a[i]);change(i,a[i]);}int m,n;while(M--){scanf("%s",c);scanf("%d%d",&m,&n);if(!strcmp("QUERY",c)){printf("%d\n",sum(n)-sum(m-1));}elsechange(m,n);}}        

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.