1. 程式人生 > >虛樹學習筆記

虛樹學習筆記

記錄 dfs序 ace inf 簡單 http stdio.h include name

虛樹算法其實原理蠻簡單的就是,從一顆n個結點的原樹上在只取出必要結點成一顆新樹,這顆新樹必包含指定m個結點並保持原樹上的祖孫關系。

首先我們來解答一些問題

問:什麽樣的結點是必要的呢??

答:指定的m個結點和 這m個結點中任意兩個結點的最近公共祖先。

問:為啥要包含最近公共祖先呢?

答:因為最近公共是虛樹的關節點,起到連接這m個點的作用

問:任意兩個結點的公共祖先構成集合,會不會有m*(m-1)/2個點?

答:其實這些祖先構成的集合最多就m-1個點,因為虛樹其實原理有點類似在樹上跑不壓縮路徑的並查集,你連接m個點最多也只需要m-1個關節點.

首先來看兩張圖,直觀感受虛樹的構建

1.構建必須包含2,4,7,6的虛樹

技術分享圖片

2.構建必須包含2,7,8的虛樹

技術分享圖片

虛樹的構建方法可以看這篇文章https://www.cnblogs.com/chenhuan001/p/5639482.html

總體上就是先按dfs排個序,然後用棧加LCA對這m個結點實現深度優先遍歷並記錄路徑以方便建虛樹。最後要註意的是建邊一定要在出棧的時候建,因為有可能出現圖2的那種情況,兩個點之間還需再塞進去一個結點。

算法復雜度:

因為虛樹構建時需要對結點按dfs序排序,並求出排序後相鄰結點的LCA的所以算法復雜度O(mlog(n*m))

虛樹優點:

虛樹主要配合樹形DP食用,因為樹形DP的復雜是與樹的結點數成正比的,通過構建虛樹,可以把樹的結點數下降到2m-1以內,從而大大提高樹形DP的效率。

代碼實現:

技術分享圖片
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<algorithm>
  4 #include<queue>
  5 #include<vector>
  6 #include<stack>
  7 using namespace std;
  8 const int MAXN=100006;
  9 /**
 10 @var d[v]    結點v的深度
 11 @var f[v][i] 結點v的第2^i個祖先
 12 @var id[v]   結點的dfs序
13 @var e[v] 結點v在原樹鄰接表 14 @var vt[v] 結點v在虛樹上包含的鄰接表 15 @var que 要查詢的結點的集合(虛樹必須包含的結點) 16 @var LN log2(最大深度)的向下取值 17 @var st 棧,用於構建ST表 18 @var z dfs序的計數器 19 註意結點標號必須從1開始,因為0拿去當空結點了。 20 */ 21 int d[MAXN],f[MAXN][18],id[MAXN],LN=0,z,st[MAXN]; 22 vector<int>e[MAXN],vt[MAXN],que; 23 int init(int n) 24 { 25 z=0; 26 memset(f,0,sizeof(f)); 27 for(int i=1; i<=n; i++) 28 e[i].clear(); 29 d[0]=-1; 30 LN=0; 31 } 32 void dfs(int v,int deep) 33 { 34 d[v]=deep; 35 st[deep]=v; 36 id[v]=++z; 37 if((1<<LN)<deep) 38 LN++; 39 int i,j; 40 for(i=1,j=0; i<=deep; i<<=1,j++) 41 { 42 f[v][j]=st[deep-i]; 43 } 44 for(i=0; i<e[v].size(); i++) 45 { 46 if(e[v][i]!=f[v][0]) 47 { 48 dfs(e[v][i],deep+1); 49 } 50 } 51 } 52 int lca(int x,int y) 53 { 54 if(d[x]<d[y]) 55 swap(x,y); 56 int i; 57 for(i=LN; i>=0; i--) 58 { 59 if(f[x][i]&&d[f[x][i]]>=d[y]) 60 x=f[x][i]; 61 } 62 if(x==y) 63 return x; 64 for(i=LN; i>=0; i--) 65 { 66 if(f[x][i]>0&&f[x][i]!=f[y][i]) 67 { 68 x=f[x][i]; 69 y=f[y][i]; 70 } 71 } 72 return f[x][0]; 73 } 74 int cmp(const int &x,const int &y) 75 { 76 return id[x]<id[y]; 77 } 78 int build(vector<int> &que) 79 { 80 int i,j,k,temp; 81 sort(que.begin(),que.end(),cmp); 82 stack<int>st; 83 st.push(que[0]); 84 vt[que[0]].clear(); 85 for(i=1; i<que.size(); i++)///每次有出棧的時候開始建邊 86 { 87 k=lca(que[i],st.top()); 88 temp=0; 89 while(!st.empty()&&lca(k,st.top())!=st.top()) 90 { 91 if(temp) 92 vt[st.top()].push_back(temp); 93 temp=st.top(); 94 st.pop(); 95 } 96 if(st.empty()||st.top()!=k) 97 { 98 st.push(k); 99 vt[k].clear(); 100 } 101 if(temp) 102 vt[st.top()].push_back(temp); 103 st.push(que[i]); 104 vt[que[i]].clear(); 105 } 106 temp=0; 107 while(!st.empty()) 108 { 109 if(temp) 110 vt[st.top()].push_back(temp); 111 temp=st.top(); 112 st.pop(); 113 } 114 return temp; 115 }
虛樹

習題:http://codeforces.com/problemset/problem/613/D

這題需要樹形DP基礎,而且要討論的情況有點多,所以其實有點不適合入門.....,因為很可能被樹形DP卡住

虛樹學習筆記