Book--LCA)
2014-10-27 16:02:46
LCA的做法不只一种,有朴素,在线倍增,离线tarjan等。
在线倍增:
void Dfs(int p,int pre,int d){ fa[0][p] = pre; dep[p] = d; for(int i = first[p]; ~i; i = e[i].next){ int v = e[i].v; if(v == pre) continue; dis[v] = dis[p] + e[i].w; Dfs(v,p,d + 1); } } void Pre(){ dis[1] = 0; Dfs(1,-1,0); for(int k = 0; k + 1 < MAX_LOG; ++k){ for(int v = 1; v <= n; ++v){ if(fa[k][v] < 0) fa[k + 1][v] = -1; else fa[k + 1][v] = fa[k][fa[k][v]]; } } } int Lca(int u,int v){ if(dep[u] > dep[v]) swap(u,v); for(int k = MAX_LOG - 1; k >= 0; --k){ if((dep[v] - dep[u]) & (1 << k)) v = fa[k][v]; } if(u == v) return u; //u为v的根 for(int k = MAX_LOG - 1; k >= 0; --k){ if(fa[k][u] != fa[k][v]){ u = fa[k][u]; v = fa[k][v]; } } return fa[0][u]; //u离lca只差一步 }
离线tarjan:
1 int Find(int x){ 2 return fa[x] == x ? x : fa[x] = Find(fa[x]); //并查集find,路径压缩 3 } 4 5 void Union(int u,int v){ 6 int x = Find(u); 7 int y = Find(v); 8 if(x != y) 9 fa[y] = x; //后者以前者为祖先 10 } 11 12 void Tarjan(int p){ 13 fa[p] = p; //先把遍历到的点p自己加入一个集合 14 for(int i = first[p]; i != -1; i = next[i]){ 15 int v = ver[i]; 16 Tarjan(v); //遍历子节点 17 Union(p,v); //因为p是其所有子节点的祖先,所以并进集合,并以p为祖先 18 } 19 vis[p] = 1; //表示以p为根的整棵子树已经访问完毕 20 for(int i = 1; i <= n; ++i) if(que[p][i] && vis[i]){ 21 cnt[Find(i)] += que[p][i]; 22 //询问的是(p,i),如果i已经访问过,那么说明遍历到lca(p,i)时 23 //是先遍历到包含i的子树,再遍历包含p的子树。那么i当前的最祖先节点必然 24 //是p和i的最近公共祖先(因为i的最祖先节点是不断更新的,一旦能发现p点, 25 //那么当前最祖先节点是最近的。) 26 27 //que[p][i] = que[i][p] = 0; 28 //上句话其实没有必要,因为一对关系只会求一次。每次关系会处理到 29 //两次,必定有一次是只有一个点遍历过,另一个点没有遍历过 30 } 31 }
另外:tarjan里面的写法还有一种,相当于访问到一个点后先查询,然后再遍历以其为根的子树。
1 void Tarjan(int p){ 2 fa[p] = p; 3 vis[p] = 1; 4 for(int i = 1; i <= n; ++i) if(que[p][i] && vis[i]){ 5 cnt[Find(i)] += que[p][i]; 6 } 7 for(int i = first[p]; i != -1; i = next[i]){ 8 int v = ver[i]; 9 Tarjan(v); 10 Union(p,v); 11 } 12 }