題目大意:
給你一顆蘋果樹,樹的主幹設為1,每一個分支設為一個數,一直到N,代表這顆蘋果樹。每個分支上面只能最多有一個蘋果,也就是一個枝子上面不可能有兩個蘋果,另外注意一點,不要把蘋果樹想象成二叉樹,蘋果樹每個節點可以分出很多叉,應該是多叉樹。
輸入是叉之間的關係,
1 2
1 3
就是主幹上面兩個叉分別是2 和3.
下面是兩種操作,Q 和C
C j 的意思是如果 j 這個枝子上面有蘋果就摘下來,如果沒有,那麼就會長出新的一個
Q j 就是問 j 這個叉上面的蘋果總數。
這個題很明顯是線段樹,其實我很多時候就用樹狀數組解決,但是,這個數組怎麼來定?????演算法很容易搞定,但是初始化是問題。
先來說一下樹狀數組:
先看一個例題:
數列操作:
給定一個初始值都為0的序列,動態地修改一些位置上的數字,加上一個數,減去一個數,或者乘上一個數,然後動態地提出問題,問題的形式是求出一段數位和.
若要維護的序列範圍是0..5,先構造下面的一棵線段樹:
可以看出,這棵樹的構造用二分便可以實現.複雜度是2*N.
每個結點用數組a來表示該結點所表示範圍內的資料之和.
修改一個位置上數位值,就是修改一個葉子結點的值,而當程式由葉子結點返回根節點的同時順便修改掉路徑上的結點的a數組的值.
對於詢問的回答,可以直接尋找i..j範圍內的值,遇到分叉時就兵分兩路,最後在合起來.也可以先找出0..i-1的值和0..j的值,兩個值減一減就行了.後者的實際操作次數比前者小一些.
這樣修改與維護的複雜度是logN.詢問的複雜度也是logN,對於M次詢問,複雜度是MlogN.
然而不難發現,線段樹的編程複雜度大,空間複雜度大,時間效率也不高.
更好的解決辦法就是用樹狀數組!
中的C數組就是樹狀數組,a數組是原數組
對於序列a,我們設一個數組C定義C[i] = a[i-2^k+1] + a[i-2^k+2] + … + a[i],k為i在二進位下末尾0的個數。
2^k的計算可以這樣:2^k = i & (i^(i-1))
inline int Lowbit(int x){ // 求x最低位1的權 return x & (x^(x-1));}定義C[i] = a[i-2^k+1]+a[i-2^k+2]+...+a[i];若要修改 a[k]的值,則C數組的修改過程如下:(n為C數組的大小)void Change(int k, int delta){ while( k<=n ){ C[k] += delta; k += LowBit(k); }}求a中1..k元素的和int Getsum(int k){ int ret = 0; while( k>0 ){ ret += C[k]; k -= Lowbit(k); } return ret;}
若要求i..j的元素和的值,則求出 1~i-1和1~j的值,相減即可
在很多的情況下,線段樹都可以用樹狀數組實現.凡是能用樹狀數組的一定能用線段樹.當題目不滿足減法原則的時候,就只能用線段樹,不能用樹狀數組.例如數列操作如果讓我們求出一段數字中最大或者最小的數字,就不能用樹狀數組了.
註:用數組儲存靜態一維線段數也可以不記錄左右孩子的下標
如0號是根節點,其孩子是1、2;
1號的孩子是3、4;
2號的孩子是5、6;
3號的孩子是7、8;
……
n號的孩子是2*n+1、2*n+2。
再說說本題如何用樹狀數組來解決:
想必大家都明白上面說的了吧,就是為了快速的查詢一個數組某一段的和,一旦這一段經過修改,依然快速給出答案。
而這個題不正是這個意思麼,原先都是一個蘋果,摘掉或者新長出,不就是改變數組的某個值麼,這時候要查詢的不就是某個區間的值嗎?
而上面的例子就是一個一維數組,但是現在如何把一個樹轉化為一維數組呢?這是這道題目的關鍵,否則沒法做。
如果理解了這一點,就好辦了。
對一棵樹進行深搜,然後將深搜的順序重新標上號,然後放到一個數組中,記下每一個枝子得起始點和終結點,然後就可以用樹狀數組了。
還是舉個例子吧。。。
如果一棵蘋果樹是這樣的。
那麼我們知道1是主幹。。。。
深搜的順序是1 4 6 5 7 2 3 當然這也是根據題目的輸入建的廣義表。
那麼當我們深搜的時候就可以在下面標上:
1 4 6 5 7 2 3 (廣義表裡面的東西,深搜結果)
1 2 3 4 5 6 7(num數組我們求和要用的)
這樣不就是樹狀數組了麼。
我們用start【1】= 1 end【1】=7代表1以上的樹枝為1-7,也就是所有
用 start【2】= 6 end【2】 = 7 代表2以上的樹枝是 3
同理
start【4】=2 end【4】 = 5 這樣我們就知道4上面的樹枝了
每一個樹枝在num【n】總數組中的位置,這樣我們就可以計算啦。。。樹狀數組,和上面將的一樣了,成功轉換
具體鍵代碼,main函數主要建廣義表,dfs主要是找到每一個樹枝在num【n】數組中的起點和終點並記錄在start和end數組中。
這樣下面就是樹狀數組的三個函數。
有人會問,為什麼要+ 或者- x & (x^(x-1)),這個我們可以看上面的規律,具體起源於什麼,我也不知道,根據上面講的就是這個數的二進位從右邊第一個1的位置。
如果還不是很明白,請回複,我會的盡量解釋,謝謝。
代碼:
#include <iostream>#include <cstdio>using namespace std;int n, m;int inc = 0;int num[100001];int start[100001];int end[100001];//要用到的廣義表的結構struct TREE {int data;TREE * next;};TREE tree[100001];void dfs(int pos){int i, j, k;start[pos] = ++ inc;TREE *p = tree[pos].next;while (p){if (start[p->data] == 0){dfs(p->data);}p = p->next;}end[pos] = inc;}//中間用到的規律值int lowBit(int x){return x&(x^(x-1));}//求從開始到這裡的和int sSum(int end){int sum = 0;while (end > 0){sum += num[end];end -= lowBit(end);}return sum;}//更新自己並且和自己相關的void change(int pos, int tmp){while (pos <= n){num[pos] += tmp;pos += lowBit(pos);}}int main(){int j,k,s,t;scanf("%d", &n);for (int i = 1; i < n; i ++){//每一個點都建一個長長的鏈表,表示自己的一個分支scanf("%d%d", &s, &t);TREE *p = new TREE;p->data = t;p->next = tree[s].next;tree[s].next = p;TREE *q = new TREE;q->data = s;q->next = tree[t].next;tree[t].next = q;}//映射到樹狀數組dfs(1);//每個初始化有一個蘋果for (int i = 1; i <= n; i ++){change(i, 1);}char ch;scanf("%d", &m);for (int i = 0; i < m; i ++){getchar();scanf("%c%d", &ch, &j);if (ch == 'C'){int sum = sSum(start[j]);sum -= sSum(start[j] - 1);if (sum == 1){change(start[j], -1);}else{change(start[j], 1);}}else{int sum = sSum(end[j]);sum -= sSum(start[j] - 1);printf("%d\n", sum);}}return 0;}