【lca】【tarjan】【模板】POJ1330模板题
这是我第一篇博客,写的肯定有点丑,轻喷(如果有人看的话。)
我们先搞懂tarjan lca算法的原理,
首先建树和记录询问信息(我看网上的代码大都由vector来完成),之后dfs这棵树,遍历树的时候,每个节点最初是没有标记的,当他以及他的所有子节点都已经被遍历后做上标记,回溯的时候,再把它加入到它父节点的集合里,这里用并查集来完成。这样的话,假定我们已经遍历了询问列表中(u,v)的u,当我们遍历到v时,由于dfs的特性,它们的最近公共祖先必然被访问过并且标记过,而u已经属于最近公共祖先的集合了,所以直接输出fa[u]即可,也许有点童鞋会问,为什么一定是最近的呢?为什么不会输出根节点呢?因为这时我们还没有完整遍历这棵子树(也就是以最近公共祖先为根节点的子树),还不会回溯到最近公共祖先的父节点,fa[最近公共祖先]还没有加入他的父节点的集合里,所以输出结果一定是最近的。
遍历整棵树需要祭出O(n)的复杂度,完成查询需要祭出O(m)的复杂度,整个算法的复杂度是O(n+m),非常的高效,而且代码也容易敲出来。
以下是代码,借鉴了http://blog.csdn.net/lanshui_yang/article/details/11746513,做了一些小小的修改。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 #include<vector> 6 using namespace std; 7 const int maxn=10000+10; 8 vector<int > son[maxn];//儿子 9 int fa[maxn];//并查集 10 int fas[maxn];//父节点个数 11 int root;//保存树的根节点 12 bool vis[maxn];//Cheak访问标记 13 int n; 14 int u,v;//保存要查询的两点 15 int find(int x) 16 { 17 if(fa[x]==x) return x; 18 return fa[x]=find(fa[x]); 19 } 20 void init() 21 { 22 memset(vis,0,sizeof(vis)); 23 for(int i=1;i<=n;i++) fa[i]=i; 24 for(int i=0;i<=n;i++) 25 { 26 son[i].clear(); 27 } 28 } 29 void read() 30 { 31 cin>>n; 32 init(); 33 for(int i=0;i<n-1;i++) 34 { 35 int a,b; 36 scanf("%d%d",&a,&b); 37 son[a].push_back(b); 38 vis[b]=true;//这里借用vis,把有入度的点标记出来 39 } 40 cin>>u>>v; 41 for(int i=1;i<=n;i++) 42 { 43 if(!vis[i])//没有入度的,为树的根节点 44 { 45 root=i; 46 break; 47 } 48 } 49 memset(vis,0,sizeof(vis));//在read函数里的vis已经完成了任务,清零 50 } 51 void tarjan_lca(int root) 52 { 53 vis[root]=true; 54 for(int i=0;i<son[root].size();i++)//遍历所有子树 55 { 56 int y=son[root][i]; 57 if(!vis[y]) 58 { 59 if(y==u && vis[v])//两个请求的节点都已经处理过 60 { 61 printf("%d\n",find(v)); 62 return; 63 } 64 if(y==v && vis[u]) 65 { 66 printf("%d\n",find(u)); 67 return; 68 } 69 tarjan_lca(y); 70 fa[y]=root; 71 } 72 } 73 } 74 int main() 75 { 76 int cas; 77 cin>>cas; 78 for(int i=1;i<=cas;i++) 79 { 80 read(); 81 tarjan_lca(root); 82 } 83 return 0; 84 }