網路上面都有這個圖,但是我將這個圖做了2點改進。
(1)圖中有一棵滿二叉樹,滿二叉樹的每乙個結點對應a中的乙個元素。
(2)c[i]為a[i]對應的那一列的最高的節點。
現在告訴你:序列c就是樹狀陣列。
那麼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中的每個元素對應滿二叉樹的每個葉子,所以我們乾脆把a中的每個元素當成葉子,那麼:c[i]=c[i]的所有葉子的和。
現在不得不引出關於二進位制的乙個規律:
先仔細看下圖:
將十進位制化成二進位制,然後觀察這些二進位制數最右邊1的位置:
1 --> 00000001
2 --> 00000010
3 --> 00000011
4 --> 00000100
5 --> 00000101
6 --> 00000110
7 --> 00000111
8 --> 00001000
1的位置其實從我畫的滿二叉樹中就可以看出來。但是這與c有什麼關係呢?
接下來的這部分內容很重要:
在滿二叉樹中,
以1結尾的那些結點(c[1],c[3],c[5],c[7]),其葉子數有1個,所以這些結點c[i]代表區間範圍為1的元素和;
以10結尾的那些結點(c[2],c[6]),其葉子數為2個,所以這些結點c[i]代表區間範圍為2的元素和;
以100結尾的那些結點(c[4]),其葉子數為4個,所以這些結點c[i]代表區間範圍為4的元素和;
以1000結尾的那些結點(c[8]),其葉子數為8個,所以這些結點c[i]代表區間範圍為8的元素和。
擴充套件到一般情況:
i的二進位制中的從右往左數有連續的x個「0」,那麼擁有2^x個葉子,為序列a中的第i-2^x+1到第i個元素的和。
終於,我們得到了乙個c[i]的具體定義:
c[i]=a[i-2^x+1]+…+a[i],其中x為i的二進位制中的從右往左數有連續「0」的個數。
理解了c[i]後,前i個元素的和s[i]就很容易實現。
從c[i]的定義出發:
c[i]=a[i-2^x+1]+…+a[i],其中x為i的二進位制中的從右往左數有連續「0」的個數。
我們可以知道:c[i]是肯定包括a[i]的,那麼:
s[i]=c[i]+c[i-2^x]+…
也許上面這個公式太抽象了,因為有省略號,我們拿乙個具體的例項來看:
s[7]=c[7]+c[6]+c[4]
因為c[7]=a[7],c[6]=a[6]+a[5],c[4]=a[4]+a[3]+a[2]+a[1],所以s[7]=c[7]+c[6]+c[4]
現在直接告訴你結論:2^x=i&(-i)
證明:設a』為a的二進位制反碼,i的二進位制表示成a1b,其中a不管,b為全0序列。那麼-i=a』0b』+1。由於b為全0序列,那麼b』就是全1序列,所以-i=a』1b,所以:
i&(-i)= a1b& a』1b=1b,即2^x的值。
x 為 i 的二進位制的最後一位1之後0的個數
假如 i=7 ,二進位制0000 0111 ,那麼x=0,s[7]=c[7]+c[7-2^0]+...
i=6 0000 0110 x=1 s[7]=c[7]+c[7-2^0]
+c[6-2^1]
i=4 0000 0100 x=2
s[7]=c[7]+c[7-2^0]
+c[6-2^1]+c[4-2^2]
s=c[7]+c[6]+c[4];
i -= 4-2^2; //不滿足i>0的條件退出迴圈
int sum(int i) //返回前i個元素和
return s;
}正如第01講提到的小石塊問題,如果陣列a[i]被更新了怎麼辦?那麼如何改動c?
如果改動c也需要o(n)的時間複雜度,那麼樹狀陣列就沒有任何優勢。所以樹狀陣列在改動c上面的時間效率為o(logn),為什麼呢?
因為改動a[i]只需要改動部分的c。這一點從第02講的圖中就可以看出來:
如上圖:
假如a[3]=3,接著a[3]+=1,那麼哪些c需要改變呢?
答案從圖中就可以得出:c[3],c[4],c[8]。因為這些值和a[3]是有聯絡的,他們用樹的關係描述就是:c[3],c[4],c[8]是a[3]的祖先。
那麼怎麼知道那些c需要變化呢?
我們來看「a」這個結點。這個「a」結點非常的重要,因為他體現了乙個關係:a的葉子數為c[3]的2倍。因為「a」的左子樹和右子樹的葉子數是相同的。 因為2^x代表的就是葉子數,所以c[3]的父親是a,a的父親是c[i+2^0],即c[3]改變,那麼c[3+2^0]也改變。
我們再來看看「b」這個結點。b結點的葉子數為2倍的c[6]的葉子數。所以b和c[6+2^1]在同一列,所以c[6]改變,c[6+2^1]也改變。
推廣到一般情況就是:
如果a[i]發生改變,那麼c[i]發生改變,c[i]的父親c[i+2^x]也發生改變。
這一行的迭代過程,我們可以寫出當a[i]發生改變時,c的更新函式為:
void update(int i,int value) //a[i]的改變值為value
}#include
using namespace std;
int a[10005];
int n;
int lowbit(int t)
void insert(int t, int d)
}int getsum(int t)
return sum;
}int main()
char str[10];
scanf("%s", str);
printf("case %d:\n", ++t);
while(strcmp(str, "end") != 0)
else if(strcmp(str, "add") == 0)
else if(strcmp(str, "sub") == 0)
scanf("%s", str);}}
return 0;
}
HDU 1166 敵兵布陣 樹狀陣列
用樹狀陣列很簡單,太晚了,貼下 睡覺去。另,研究線段樹的時候,發現網上流傳著有幾種不同的線段樹,最正宗的是以單位區間為單位,只能處理線段 另外還有幾種葉子結點是點的,這種也可以用來處理點,所以這題是可以用這種線段樹做的。還搞不太清楚它們之間的關係。mark一下,明天再說。include includ...
HDU 1166 敵兵布陣 樹狀陣列
problem description c國的死對頭a國這段時間正在進行軍事演習,所以c國間諜頭子derek和他手下tidy又開始忙乎了。a國在海岸線沿直線布置了n個工兵營地,derek和tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數c國都掌握的...
HDU 1166 敵兵布陣 (樹狀陣列)
敵兵布陣 time limit 1000msmemory limit 32768kb64bit io format i64d i64u submit status description c國的死對頭a國這段時間正在進行軍事演習,所以c國間諜頭子derek和他手下tidy又開始忙乎了。a國在海岸線沿...