樹狀陣列相關應用之多叉樹子樹問題
阿新 • • 發佈:2018-12-06
Poj-3321:Apple Tree
題意:
卡卡的房子外面有一棵蘋果樹。每年秋天,樹上都會長出許多蘋果。卡卡非常喜歡蘋果,所以他一直在精心培育著這棵大蘋果樹。
這棵樹有N個分叉,這些分叉由樹枝相連。卡卡把叉的編號從1到N,根編號總是1。蘋果會在叉子上生長,兩個蘋果不會在同一個叉子上生長。卡卡想知道一個子樹上有多少個蘋果,因為他研究的是蘋果樹的生產能力。
問題是一個新的蘋果可能會在空叉子上生長,卡卡可能會從樹上摘一個蘋果作為甜點。你能幫卡卡嗎?
輸入
第一行包含一個整數N(N≤100000),這是樹中的分支的數量。
下面的N - 1行每一行都包含兩個整數u和v,這意味著fork u和fork v由一個分支連線。
下一行包含一個整數M(≤100000)。
以下M行每一行都包含一個訊息
C x表示叉x上的蘋果已經改變了。比如,如果叉子上有一個蘋果,卡卡就會把它摘下來;否則,空叉子上就會長出一個新的蘋果。
或
“Q x”表示在叉x上方的子樹中查詢蘋果的數量,包括叉x上的蘋果(如果存在的話)
注意一開始樹裡滿是蘋果
輸出
對於每個查詢,輸出每行對應的答案。
解題思路:
先利用鏈式向前星將樹結構儲存起來,再用dfs獲取dfs序轉成線性結構,之後便可用樹狀陣列通過dfs序對樹進行維護,更改狀態位置為beg【i】,求子樹蘋果數的線性範圍為beg【i】–last【i】。
#include <iostream> #include <cstring> #include <stdio.h> #define MAXSIZE 100005 using namespace std; //使用名稱空間std int Head[MAXSIZE], Next[MAXSIZE], To[MAXSIZE]; //鏈式向前星(線性結構存邊) //Head[i]代表起點為i的邊在向前星中最後儲存的位置(頭指標),To[i]表示向前星第i個位置的邊的終點,Next[i]表示與第i個邊相同起點的下一個邊在向前星中的位置 int beg[MAXSIZE], last[MAXSIZE]; //begin[i]和last[i]分別儲存以i為起點的連通子圖(樹結構為子樹)的dfs序的起始位置和終止位置 int cnt_edg, cnt_dfs; //向前星下標(0--MAXSIZE),dfs序儲存下標(1--MAXSIZE)(對應樹狀陣列) bool tree[MAXSIZE]; //蘋果樹蘋果的狀態(0/1) int val[MAXSIZE]; //樹狀陣列 int main() { void add(int from, int to); void dfs(int fa, int son); int lowbit(int x); void update_1(int val[], int i, int cal, int arry_num); int sum_pre_1(int val[], int i); int sum_between_1(int val[], int pre, int last); int N, M; //N個分叉,M個指令 int x1, x2; int x; char order; //指令 while (~scanf("%d", &N)) { memset(Head, -1, sizeof(Head)); memset(val, 0, sizeof(val)); cnt_edg = 0; cnt_dfs = 0; for(int i=1;i<N;i++) {//加邊 scanf("%d%d", &x1, &x2); add(x1, x2); } dfs(-1, 1); //獲取dfs序 for (int i = 1;i <= N;i++) { tree[i] = true; update_1(val, i, 1, N); } //蘋果樹初始化完畢 scanf("%d", &M); while (M--) { getchar(); //取出緩衝區的換行符 scanf("%c%d", &order, &x); if (order == 'C') {//改變x枝的蘋果狀態 if (tree[x]) { tree[x] = 0; update_1(val, beg[x], -1, cnt_dfs); } else { tree[x] = 1; update_1(val, beg[x], 1, cnt_dfs); } } else printf("%d\n", sum_between_1(val, beg[x], last[x])); } } return 0; } //鏈式向前星 void add(int from, int to) {//加邊(from:起點/to:終點) To[cnt_edg] = to; //cnt為向前星下標計數器(第cnt條邊,cnt從0開始) Next[cnt_edg] = Head[from]; Head[from] = cnt_edg++; } void dfs(int fa, int son) {//圖遍歷:fa:父結點,son:子結點 (以son為起點遍歷) (樹結構是遍歷son對應的子樹) //獲得一個以son為起點的dfs序() beg[son] = ++cnt_dfs; for (int i = Head[son];~i;i = Next[i]) //for迴圈是son的廣度 ~i:i!=-1 (~非運算子,-1的補碼全為1) if (fa != To[i]) dfs(son, To[i]); //廣度的每個子樹對應一個深度 last[son] = cnt_dfs; //(單個深度遍歷完畢) } //一維樹狀陣列 int lowbit(int x) {//返回二進位制數最低位的1對應的數值 return x & (-x); //與運算 } //一維樹狀陣列 void update_1(int val[], int i, int cal, int arry_num) {//原陣列第i個元素加上cal,更新樹狀陣列相關元素,arry_num為原陣列的長度 //可直接用於樹狀陣列的建立 for (;i <= arry_num;i += lowbit(i)) val[i] += cal; } int sum_pre_1(int val[], int i) {//求arry陣列的前i項和 //val為樹狀陣列地址 int sum = 0; for (;i > 0;i -= lowbit(i)) //從後向前每次跳一個lowbit sum += val[i]; return sum; } int sum_between_1(int val[], int pre, int last) {//求原陣列arry在區間[pre-last]的和 return sum_pre_1(val, last) - sum_pre_1(val, pre - 1); }