倍增(lca模板)
http://poj.org/problem?id=1330
题意:给出一颗n个节点的树,问u、v的lca。
解法:倍增:
1、两数组fa[i][j]表示i节点的长为2j的祖先,de[u]记录节点深度。
2、dfs遍历预处理de[u] , 有fa[u][i] = fa[fa[u][i-1]][i-1] 表示u节点的长为2i的祖先为,u节点长为2i-1的祖先的长为2i-1的祖先。
3、在线询问:lca(u,v):
4、先将深度大的点向上以2的幂次长向上跳,根据整数分解(或则二进制)性质可知,深度大的点会慢慢逼近深度小的点并最终位于同一深度。
5、此时特判u、v是否重合,重合即v为lca。
6、否则两点同时往上跳,根据性质可知一定可以跳到u、v的lca的下一节点,所以fa[u][0]表示u的父节点即为lca。
疑问:为什么一定可以跳到lca的下一节点呢?
比如两节点此时在同一深度,距离lca长度为4,二进制表示为100,这两点同时跳2,再跳1.就达到目的。
根据二进制性质可知相差任意距离都可以达到目的。
疑问:为什么不直接逃到 fa[u][i] == fa[v][j] 这个点呢?
因为跳到的这个点不一定是最近的公共祖先.
注意:数组fa也要初始化。
思路:倍增是在朴素算法上的一个优化,朴素算法就是两个结点一步一步跳,而倍增是利用二进制性质一大步一大步跳,而一大步一大步跳就需要知道跳到了哪个结点
所以需处理出fa[i][j]表示i结点往上跳2的j次方步到达的结点。
https://blog.csdn.net/Q_M_X_D_D_/article/details/89924963
#include<bits/stdc++.h> using namespace std ; const int N = 40010 , M = 80010 ; int fa[N][30] , n , q , dep[N] ; int e[M] , ne[M] , h[N] , idx; void add(int a , int b){ e[idx] = b , ne[idx] = h[a] , h[a] = idx++; } void dfs(int u , int pre){//0作为一个虚根 dep[u] = dep[pre] + 1 ; fa[u][0] = pre; for(int i = 1 ; (1 << i) <= dep[u] ; i++){//最多跳到虚根 fa[u][i] = fa[fa[u][i-1]][i-1]; } for(int i = h[u] ; ~i ; i = ne[i]){ int j = e[i] ; if(j == pre) continue; dfs(j , u); } } int lca(int a , int b){ if(dep[a] < dep[b]) swap(a , b); for(int i = 20 ; i >= 0 ; i--){//跳到同一深度 if(dep[a] - (1 << i) >= dep[b]){ a = fa[a][i]; } } if(a == b) return a ; for(int i = 20 ; i >= 0 ; i --){//跳到最近公共祖先 if(fa[a][i] != fa[b][i]){ a = fa[a][i]; b = fa[b][i]; } } return fa[a][0]; } int main(){ #ifdef ONLINE_JUDGE #else freopen("D:\\c++\\in.txt", "r", stdin); //freopen("D:\\c++\\out.txt", "w", stdout); #endif memset(h , -1 , sizeof(h)); cin >> n ; int rt ; for(int i = 1 ; i <= n ; i++){ int a , b ; cin >> a >> b ; if(b == -1){ rt = a ; continue; } add(a , b); add(b , a); } dfs(rt , 0); cin >> q; for(int i = 1 ; i <= q ; i++){ int a , b ; cin >> a >> b ; cout << lca(a ,b) << endl; } }