概念:
給出一棵樹.
每次詢問選擇一些點,求一些東西.這些東西的特點是,許多未選擇的點可以通過某種方式剔除而不影響最終結果.
於是就有了建虛樹這個技巧.....
我們可以用log級別的時間求出點對間的lca....
那么,對於每個詢問我們根據原樹的信息重新建樹,這棵樹中要盡量少地包含未選擇節點. 這棵樹就叫做虛樹.
接下來所說的"樹"均指虛樹,原來那棵樹叫做"原樹".
構建過程如下:
按照原樹的dfs序號(記為dfn)遞增順序遍歷選擇的節點. 每次遍歷節點都把這個節點插到樹上.
首先虛樹一定要有一個根. 隨便扯一個不會成為詢問點的點作根.(並不覺得是這樣)
維護一個棧,它表示在我們已經(用之前的那些點)構建完畢的虛樹上,以最后一個插入的點為端點的DFS鏈.
設最后插入的點為p(就是棧頂的點),當前遍歷到的點為x.我們想把x插入到我們已經構建的樹上去.
求出lca(p,x),記為lca.有兩種情況:
1.p和x分立在lca的兩棵子樹下.
2.lca是p.
(為什么lca不能是x?
因為如果lca是x,說明dfn(lca)=dfn(x)<dfn(a),而我們是按照dfs序號遍歷的,於是dfn(a)<dfn(x),矛盾.)
對於第二種情況,直接在棧中插入節點x即可,不要連接任何邊(后面會說為什么).
對於第一種情況,要仔細分析.
我們是按照dfs序號遍歷的(因為很重要所以多說幾遍......),有dfn(x)>dfn(p)>dfn(lca).
這說明什么呢? 說明一件很重要的事:我們已經把lca所引領的子樹中,p所在的子樹全部遍歷完了!
簡略的證明:如果沒有遍歷完,那么肯定有一個未加入的點h,滿足dfn(h)<dfn(x),
我們按照dfs序號遞增順序遍歷的話,應該把h加進來了才能考慮x.
這樣,我們就直接構建lca引領的,p所在的那個子樹. 我們在退棧的時候構建子樹.
p所在的子樹如果還有其它部分,它一定在之前就構建好了(所有退棧的點都已經被正確地連入樹中了),就剩那條鏈.
如何正確地把p到lca那部分連進去呢?
設棧頂的節點為p,棧頂第二個節點為q.
重復以下操作:
如果dfn(q)>dfn(lca),可以直接連邊q->p,然后退一次棧.
如果dfn(q)=dfn(lca),說明q=lca,直接連邊lca->p,此時子樹已經構建完畢.
如果dfn(q)<dfn(lca),說明lca被p與q夾在中間,此時連邊lca->q,退一次棧,再把lca壓入棧.此時子樹構建完畢.
如果不理解這樣操作的緣由可以畫畫圖.....
最后,為了維護dfs鏈,要把x壓入棧. 整個過程就是這樣.....
下圖是自己畫的一個例子:

附上模板:
///////////--虛樹模板--/////////////////// //傳入樹的一個子集,若以按dfs序排好直接調用build_vtree //否則調用vsort //復雜度O( nlog(n) ) n是虛樹的大小 #define N 11000 #define LN 20 ////////////--標准建鄰接表--///////////// struct node { int to,next; }edge[2*N]; int cnt,pre[N]; void add_edge(int u,int v) { edge[cnt].to = v; edge[cnt].next = pre[u]; pre[u] = cnt++; } ////////////////////////////////////// int deep[N];//記錄每個點的深度 int order[N];//記錄每個點的訪問次序 int indx=0; struct Lca_Online { int _n; int dp[N][LN]; void _dfs(int s,int fa,int dd) { deep[s] = dd; order[s] = ++indx; for(int p=pre[s];p!=-1;p=edge[p].next) { int v = edge[p].to; if(v == fa) continue; _dfs(v,s,dd+1); dp[v][0] = s; } } void _init() { for(int j=1;(1<<j)<=_n;j++) { for(int i=1;i<=_n;i++) { if(dp[i][j-1]!=-1) dp[i][j] = dp[ dp[i][j-1] ][j-1]; } } } void lca_init(int n) { _n = n; memset(dp,-1,sizeof(dp)); //_dfs(firstid,-1,0); indx = 0; _dfs(1,-1,0); _init(); } int lca_query(int a,int b) { if(deep[a]>deep[b]) swap(a,b); //調整b到a的同一高度 for(int i=LN-1;deep[b]>deep[a];i--) if(deep[b]-(1<<i) >= deep[a]) b = dp[b][i]; if(a == b) return a; for(int i=LN-1;i>=0;i--) { if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i]; } return dp[a][0]; } }lca; int stk[N],top; int mark[N];//標示虛樹上的點是否是無用點 vector<int>tree[N];//存邊 vector<int>treew[N];//存權 void tree_add(int u,int v,int w) { tree[u].push_back(v); tree[v].push_back(u); treew[u].push_back(w); treew[v].push_back(w); } //使用前調用 lca.lca_init(n); 初始化 //返回虛樹根節點,虛樹的邊默認為原樹上兩點的距離 int build_vtree(int vp[],int vn)//傳入按dfs序數組,以及長度(要自己寫按dfs排序的數組) { if(vn == 0) return -1; top = 0; stk[top++] = vp[0]; tree[ vp[0] ].clear(); treew[ vp[0] ].clear(); mark[ vp[0] ]=1; for(int i=1;i<vn;i++) { int v = vp[i]; int plca = lca.lca_query(stk[top-1], v);//最近公共祖先 if(plca == stk[top-1]) ;//不處理 else { int pos=top-1; while(pos>=0 && deep[ stk[pos] ]>deep[plca]) pos--; pos++; for(int j=pos;j<top-1;j++) { tree_add(stk[j],stk[j+1],deep[stk[j+1]]-deep[stk[j]]); } int prepos = stk[pos]; if(pos == 0) { tree[plca].clear(),treew[plca].clear(),stk[0]=plca,top=1; mark[plca] = 0; } else if(stk[pos-1] != plca) { tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+1; mark[plca] = 0; } else top = pos; tree_add(prepos,plca,deep[prepos]-deep[plca]); } tree[v].clear(); treew[v].clear(); stk[top++] = v; mark[v] = 1; } for(int i=0;i<top-1;i++) { tree_add(stk[i], stk[i+1], deep[stk[i+1]]-deep[stk[i]]); } return vp[0]; } //////////////--先排序,再建虛樹--////////////////////// struct vnode { int order,id; }vg[N]; int vcmp(vnode t1,vnode t2) { return t1.order<t2.order; } int vsort(int vp[],int vn)//傳入未排序的數組,以及長度. { for(int i=0;i<vn;i++) vg[i].id = vp[i],vg[i].order = order[ vp[i] ]; sort(vg,vg+vn,vcmp); for(int i=0;i<vn;i++) vp[i]=vg[i].id; return build_vtree(vp, vn); } //////////////////////////////////// //void dfs(int s,int fa) //{ // printf("%d ",s); // for(int i=0;i<tree[s].size();i++) // { // int v = tree[s][i]; // if(v == fa) continue; // dfs(v,s); // } //} // //int main() //{ // int n; // cin>>n; // cnt = 0; // memset(pre,-1,sizeof(pre)); // for(int i=1;i<n;i++) // { // int a,b; // cin>>a>>b; // add_edge(a, b); // add_edge(b, a); // } // int m; // cin>>m; // int save[100]; // for(int i=0;i<m;i++) scanf("%d",save+i); // lca.lca_init(n); // int root = vsort(save, m); // if(root != -1) // { // dfs(root,-1); // } // return 0; //} /* 7 1 3 1 2 2 6 3 5 3 4 4 7 3 6 7 5 */
