標籤:線段樹 樹狀數組
題目描述 Description
在卡卡的房子外面,有一棵蘋果樹。每年的春天,樹上總會結出很多的蘋果。卡卡非常喜歡吃蘋果,所以他一直都精心的呵護這棵蘋果樹。我們知道樹是有很多分叉點的,蘋果會長在枝條的分叉點上面,且不會有兩個蘋果結在一起。卡卡很想知道一個分叉點所代表的子樹上所結的蘋果的數目,以便研究蘋果樹哪些枝條的結果能力比較強。
卡卡所知道的是,每隔一些時間,某些分叉點上會結出一些蘋果,但是卡卡所不知道的是,總會有一些調皮的小孩來樹上摘走一些蘋果。
於是我們定義兩種操作:
C x
表示編號為x的分叉點的狀態被改變(原來有蘋果的話,就被摘掉,原來沒有的話,就結出一個蘋果)
Qx
查詢編號為x的分叉點所代表的子樹中有多少個蘋果
我們假定一開始的時候,樹上全都是蘋果,也包括作為根結點的分叉1。
輸入描述 Input Description
第一行一個數N (n<=100000)
接下來n-1行,每行2個數u,v,表示分叉點u和分叉點v是直接相連的。
再接下來一行一個數M,(M<=100000)表示詢問數
接下來M行,表示詢問,詢問的格式如題目所述Q x或者C x
輸出描述 Output Description
對於每個Q x的詢問,請輸出相應的結果,每行輸出一個
範例輸入 Sample Input
3
1 2
1 3
3
Q 1
C 2
Q 1
範例輸出 Sample Output
3
2
- 題解
- 一看這資料規模和輸入方式就基本可以確定是一道進階資料結構題。做了不少關於樹的題目,自己感覺樹其實對應著一段區間,即其遍曆順序,很多種維護方式可以基於這個遍曆順序進行。
- 二叉樹的中序遍曆比較特殊,對此Noip2003加分二叉樹一題已充分體現。那個題目用了中序遍曆進行區間dp,並取得了良好的效果。
- 而普通的樹並沒有中序遍曆,但其本身的先根遍曆與後根遍曆也有諸多特殊性。
- 先對樹進行一次深度優先遍曆,用dfn數組(時間戳記)標記整棵樹遍曆時訪問結點的順序,因為一棵子樹的訪問順序是連續的!
- 這樣一棵子樹就可以被當作一段區間來提取。本題只涉及樹上單個結點的修改與和的查詢,所以立刻可以想到樹狀數組和線段樹。
- 如果使用先根遍曆,那麼需要記錄每棵子樹下最後被訪問到的結點的時間戳記,這樣這棵子樹可以用子樹的根的時間戳記和子樹下最後被訪問到的結點的時間戳記在先根遍曆得到的序列中提取出來。同理,使用後根遍曆需要記錄每棵子樹最先被訪問到的結點的時間戳記。
下面就好解決了:
C操作,直接對著時間戳記修改在遍曆順序中相應的結點即可;
Q操作,直接求時間戳記在遍曆順序中對應的區間和即可;
Code——————用zkw線段樹維護後根遍曆序列
#include <cstdio>#include <cstring>#include <vector>#include <iostream>#include <algorithm>using namespace std;const int maxn = 100010, nil = 0, root = 1;int n, m, lc[maxn], dfn[maxn];int T[maxn << 2], delta, tot; //zkw線段樹,tot是訪問順序vector <int> tree[maxn];bool *vis; //因為不明確給出的邊的父子關係,所以雙向添邊,遍曆時這個vis記錄結點是否被訪問過int dfs(int rot) //返回以rot為根的子樹的最左邊的兒子{ int rt = 0; bool b = false; vis[rot] = true; for(int i = 0; i < tree[rot].size(); ++i) if(!vis[tree[rot][i]]) { b = true; rt = dfs(tree[rot][i]); if(lc[rot] == nil) lc[rot] = rt; } dfn[rot] = ++tot; if(b) return lc[rot]; else return lc[rot] = dfn[rot];}void init(){ int u, v; cin >> n; //線段樹初始化 for(delta = 2; delta <= (n << 1) + 1; delta <<= 1); for(int i = delta + 1; i <= delta + n; ++i) T[i] = 1; for(int i = delta - 1; i >= 1; --i) T[i] = T[i << 1] + T[i << 1 | 1]; //初始化結束 for(int i = 1; i < n; ++i) { cin >> u >> v; tree[u].push_back(v); tree[v].push_back(u); } vis = new bool[maxn]; memset(vis, false, sizeof(vis)); dfs(root); delete [] vis; vis = NULL; for(int i = 1; i <= n; ++i) tree[i].clear(); //少用點記憶體,對評測機溫柔點 cin >> m;}void change(int x) //單點修改{ for(T[x += delta] ^= 1, x >>= 1; x; x >>= 1) T[x] = T[x << 1] + T[x << 1 | 1];}int query(int l, int r) //區間查詢{ if(l == r) return T[l + delta]; int ans = 0; for(l += delta - 1, r += delta + 1; l ^ r ^ 1; l >>= 1, r >>= 1) { if(~l & 1) ans += T[l ^ 1]; if(r & 1) ans += T[r ^ 1]; } return ans;}void work(){ char opt; int x; while(m--) { cin >> opt >> x; if(opt == ‘Q‘) cout << query(lc[x], dfn[x]) << endl; else change(dfn[x]); }}int main(){ init(); work(); return 0;}
Codevs1128蘋果樹題解