代碼短小精悍,理解費勁= =.
總結下思路:
首先將相連的邊存起來( insert ):
用數組模擬一個單向鏈表,因而每一條邊存兩次;
鏈表的節點代表一條邊,有兩個值:蘋果樹的下一分叉點 & 當前節點上連的上一條邊.
一條邊的"地址"就是當前分叉點的加入序號(因為是用數組模擬的,也就是下標尋址).***
由於每一個分叉點的編號(除1外)只是一個id,故使用list數組實現id尋址.list值為id分叉點對應的最末加入的一條邊的地址. ***這裡很繞= =b注意順序
tree[ list[ id ] ] 就是id分叉點最末加入的一條邊啦.
注意由於list是全局變量,自動初始化為全0,也就是第一條邊的next為0邊.
同一個id分叉點的不同邊之間是直接用鏈表的next尋址的;
不同id分叉點的邊是直接借助list數組用id尋址的(只找到最末那條邊~).
然後把每一個id打上時間戳( make ):
這裡當然也是id尋址.
用一個結構體數組存儲每一個id對應的[進入,退出]時間.本代碼選擇退出增一,進入不變.反之也可~
這個過程要模擬DFS.
簡單來說就是;
1 總是找當前節點的下一個節點,直到葉子節點;
2 到達葉子節點後,記錄時間戳,入值==出值,出值++;返回上一分叉;
3 檢查是否有下一條邊(即找下一節點),若有,遞歸返回步驟1.
若沒有,入值=最小入值**,出值=最大出值++**; 返回上一分叉; **由於遞歸,這裡的數值變化不容易看清楚.
調用時 make( 0, 1 ),因此最後執行到步驟3時就會完全退出.
下面就是建立一個滿的線段樹(初始時每個分叉都有一個蘋果).
詢問時,根據tab找到此id對應的區間.詢問即可.
更改時,單點異或 tab[ id ].r 就行了(因為是對應時間戳的出值).
#include <cstdio> #include <cstring> using namespace std; #define maxn 110000 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 /** 除了知道1為根節點之外其他編號都只能認為是id,數字沒有指導意義.可得唯一解. **/ int min(int a, int b) { return a + ((b-a) & ((b-a) >> 31)); } struct branch{///樹枝是單向的 int next,node; }tree[maxn<<1];//由樹枝組成的樹 struct Node{ int l,r; }tab[maxn];//由節點組成的表 int cnt,sum;//樹枝計數 ///sum是時間戳的游標 int list[maxn],seg[maxn<<2];//seg是線段樹,list是..?(下文) inline void insert(int fro,int to)//插入一條樹枝 { tree[cnt].node = to;///node指向目標節點的id tree[cnt].next = list[fro];///最初list[0]是0,此後都是fro對應樹枝的下標 ///而此樹枝的next存的是上一次遺留下來的tree下標,由此可以找到上一條由fro引出的樹枝!!! list[fro] = cnt++ ;//list是用id做index的..存的是tree下標 ///看next的時候應該猜想"node"存的是節點id,而本身是樹枝,所以next應該是指向樹枝的指針 ///而結構體的next是單向的指向下一節點,所以這裡的next也是單向的回溯,只不過是上一個樹枝 } int make(int pre,int now) {///l是進入時間戳,r是返回時間戳.這裡進入不計時間,返回才增 int p = list[now],mi = 2*maxn,t;///list指向最後一條邊 while(p){///list數組是初始化為0的,p==0說明已到達最後一條邊 t = tree[p].node;///t指向從now出發的下一節點 if(t!=pre)mi = min(make(now,t),mi);///對make總是傳入相鄰的節點參數 ///這裡取min是為了一直保留進入時的時間戳,遞歸結束往回返的時候mi保留著, ///如果找到了下一個兒子,當且僅當每到一個葉子節點,mi會++ p = tree[p].next;///t==pre的話就停止遞歸,p==tree[p].next==0 ///由於之前調用insert的時候,是連續(a,b)(b,a)的,所以, ///如果到達了"葉子樹枝"就會出現t==pre的狀況!!! ///當然,t!=pre的話遞歸完了也得走這裡往回退 ///這個時候就是找下一個樹枝,繼續...SOGA!!這樣就模擬了DFS啊!!!= =b } mi = min(mi,sum);///退出的時候,葉子節點左右相等 均為sum. ///到達葉子節點時mi在這裡會變大!!! tab[now].l = mi,tab[now].r = sum++;///tree和list的作用僅限於建時間戳 ///到這裡,右端點總是++的,葉子節點的話就取mi(上面mi取了INF和sum的最小,是sum) ///不是葉子節點的話,就取實際的sum(兒子全部找完了退出的) return mi;///返回mi,找兒子的時候mi是一樣的! } void update(int p,int l,int r,int rt) { if(l==r){//XOR 1 seg[rt] ^= 1 ; return ; } int m = (l+r) >> 1; if(p<=m)update(p,lson); else update(p,rson); seg[rt] = seg[rt<<1] + seg[rt<<1|1]; } int query(int L,int R,int l,int r,int rt) { if(L<=l&&R>=r)return seg[rt]; int m = (l+r) >> 1,ret = 0; if(m>=L)ret += query(L,R,lson); if(m<R)ret+= query(L,R,rson); return ret; } void build(int l,int r,int rt) { if(l==r){ seg[rt] = 1; return ; } int m = (l+r) >> 1; build(lson),build(rson); seg[rt] = seg[rt<<1] + seg[rt<<1|1];//PushUp(rt); } int main() { int n; while(scanf("%d",&n)!=EOF){ //if(n==0)continue; int a,b; sum = cnt = 1; for(int i=1;i<n;i++){ scanf("%d%d",&a,&b); insert(a,b); insert(b,a); } make(0,1);//功能應該是把一條條樹枝拼接起來,成為一顆蘋果樹,然後進行DFS遍歷,得到時間戳 build(1,n,1);//功能應該是建立線段樹,這個線段樹是標准求和線段樹,本身和蘋果樹沒啥關系 //只是通過蘋果樹的DFS遍歷給蘋果樹的每個節點打上時間戳,然後在蘋果樹中找應該查詢的區間 //update的時候就是在線段樹中單點替換了,因為區間的對應是不會變的 int m,t; char s[2]; scanf("%d",&m); while(m--){ scanf("%s%d",s,&t); if(s[0]=='Q')printf("%d\n",query(tab[t].l,tab[t].r,1,n,1)); //線段樹seg是該段的蘋果數 //tab中存的是時間戳,tab是以id尋址的.. else { update(tab[t].r,1,n,1); } } } return 0; }