【LCA最近公共祖先】在线离线
【在线】
1.倍增法
现将深度较大的跳至与深度较小的统一深度。预处理$fa[u][i]$表示$u$往上跳$2^i$个单位后的祖先,则就可以像快速幂一样,将移动的步数化为二进制,如果第$i$位为$1$,那么向上跳$2^i$次方,即$if(1 << i \& d) u = fa[u][i]$。跳至统一深度后,若两点重合,则返回两点的任意一个。若不重合,再一个一个一起往上跳,直到重合。
复杂度为$O(N*logN) $
【code】求两点距离
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
using namespace std; const int N = ;
int dep[N], dis[N];
int ecnt, adj[N], go[N << ], len[N << ], nxt[N << ];
int fa[N][], Log[N], n, m; inline void addEdge(const int &u, const int &v, const int &l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline void Init_Log(){
Log[] = -;
for(int i = ; i <= n; i++) Log[i] = Log[i >> ] + ;
} inline void dfs(const int &u, const int &f, const int &l){
dep[u] = dep[f] + ;
dis[u] = dis[f] + l;
fa[u][] = f;
for(int i = ; fa[u][i]; i++)
fa[u][i + ] = fa[fa[u][i]][i];
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs(v, u, len[e]);
}
} inline int lca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
int delta = dep[u] - dep[v];
for(int i = Log[delta]; i >= ; i--)
if( << i & delta) u = fa[u][i];
if(u == v) return u;
for(int i = Log[dep[u]]; i >= ; i--)
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][];
} int main(){
scanf("%d%d", &n, &m);
Init_Log();
for(int i = ; i < n; i++){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addEdge(x, y, z);
}
dfs(,,);
for(int i = ; i <= m; i++){
int x, y;
scanf("%d%d", &x, &y);
int L = lca(x, y);
cout<<(dis[x] - dis[L]) + (dis[y] - dis[L])<<endl;
}
return ;
}
2.树链剖分
同样,将点往上跳,不过树链剖分后可以直接从重链尾部跳到重链顶部甚至下一条重链的尾部,直到两点在同一重链上,先判重合,否则就是现在深度较小的点。
复杂度O(mlog2 n)
【code】求两点距离
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std; const int N = 1e5 + ;
const int oo = 0x3f3f3f3f; int dep[N], sze[N], top[N], son[N], pos[N], idx[N], val[N], fa[N];
int ecnt, adj[N], go[N << ], nxt[N << ], tot, len[N << ];
int n, m, dis[N]; inline int Re(){
int i = , f = ; char ch = getchar();
for(; (ch < '' || ch > '') && ch != '-'; ch = getchar());
if(ch == '-') f = -, ch = getchar();
for(; ch >= '' && ch <= ''; ch = getchar())
i = (i << ) + (i << ) + (ch - '');
return i * f;
} inline void Wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) Wr(x / );
putchar(x % + '');
} inline void addEdge(const int &u, const int &v, const int &l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline void dfs1(const int &u, const int &f, const int &l){
dep[u] = dep[f] + ;
dis[u] = dis[f] + l;
fa[u] = f;
sze[u] = ;
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs1(v, u, len[e]);
sze[u] += sze[v];
if(sze[v] > sze[son[u]]) son[u] = v;
}
} inline void dfs2(const int &u, const int &f){
if(son[u]){ //先查重儿子, 保证重链连续
top[son[u]] = top[u];
idx[pos[son[u]] = ++tot] = son[u];
dfs2(son[u], u);
}
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f || v == son[u]) continue;
top[v] = v;
idx[pos[v] = ++tot] = v;
dfs2(v, u);
}
} inline int lca(int u, int v){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
if(u == v) return u;
if(dep[u] < dep[v]) return u;
else return v;
} int main(){
// freopen("h.in", "r", stdin);
n = Re(), m = Re();;
for(int i = ; i < n; i++){
int a = Re(), b = Re(), c = Re();
addEdge(a, b, c);
}
dfs1(, , );
pos[] = top[] = idx[] = tot = , dep[] = -;
dfs2(, );
for(int i = ; i <= m; i++){
int a = Re(), b = Re();
int L = lca(a, b);
Wr(dis[a] + dis[b] - * dis[L]), putchar('\n');
}
return ;
}
【离线】
【tarjan】
奇妙的算法。但要求必须离线,先记录下所有的询问,再挨个找到答案。
用$dfs$的思想,现将子树扫描完,再返回根节点,进入下一颗子树。
tarjan求lca则每扫描完一颗子树,就将他与根节点的并查集进行合并,然后处理有关询问(可能现在还没法回答)。
如下图所示:比如我要查找$(4, 5), (4, 6)$的$lca$
扫描完$4$这颗子树后,4的并查集祖先已经设置为2,就可以开始尝试处理$4$中 的询问了

但是,处理4-6, 4-5询问时,发现5、6还没被访问到,所以回答失败,继续dfs。

扫描完5,处理询问,发现4已经访问过,而我dfs时从4返回到2,4的并查集祖先已经设置为2,然后我跨过2到达5,所以4和5的祖先一定就是getAnc(4) = 2.
4-6同理,,2的祖先被设置为1,那么4通过并查集的维护最终祖先也变为1,跨过1后到达6,那么4-6的lca一定为1.
看懂了这个算法,就明白为什么必须要求离线了。
【code】求两点lca
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std; #define mp make_pair
const int N = ;
int ecnt, adj[N], go[N << ], nxt[N << ], len[N << ];
int n, m, qa[N], qb[N], qans[N];
vector<pair<int, int> > vq[N];
int anc[N], dis[N];
bool vst[N]; int read(){
int i=,f=;char ch;
for(ch=getchar();(ch<''||ch>'')&&ch!='-';ch=getchar());
if(ch=='-') {f=-;ch=getchar();}
for(;ch>=''&&ch<='';ch=getchar()) i=(i<<)+(i<<)+(ch^);
return f*i;
} inline void wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) wr(x / );
putchar(x % + '');
} inline void addEdge(int u, int v, int l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline int getAnc(int u){
return (u == anc[u]) ? u : (anc[u] = getAnc(anc[u]));
} inline void Merge(int u, int v){
int fu = getAnc(u), fv = getAnc(v);
if(fu != fv) anc[fu] = fv;
} inline void tarjan(int u, int f, int d){
dis[u] = dis[f] + d;
for(int i = adj[u]; i; i = nxt[i]){
if(go[i] == f) continue;
if(!vst[go[i]]){
tarjan(go[i], u, len[i]);
Merge(go[i], u);
anc[getAnc(u)] = u;
}
}
vst[u] = true;
for(int i = ; i < vq[u].size(); i++){
if(vst[vq[u][i].first])
qans[vq[u][i].second] = getAnc(vq[u][i].first);
}
} int main(){
n = read(), m = read();
for(int i = ; i < n; i++){
int a, b, c;
a = read(), b = read(), c = read();
addEdge(a, b, c);
}
for(int i = ; i <= m; i++){
int u, v;
u = read(), v = read();
qa[i] = u, qb[i] = v;
vq[u].push_back(mp(v, i)), vq[v].push_back(mp(u, i));
}
for(int i = ; i <= n; i++) anc[i] = i;
tarjan(, , );
for(int i = ; i <= m; i++){
int ans = dis[qa[i]] + dis[qb[i]] - * dis[qans[i]];
wr(ans), putchar('\n');
}
return ;
}
【LCA最近公共祖先】在线离线的更多相关文章
- LCA 最近公共祖先 tarjan离线 总结 结合3个例题
在网上找了一些对tarjan算法解释较好的文章 并加入了自己的理解 LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点.也就是说,在两个点通 ...
- LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现
首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵 ...
- 求LCA最近公共祖先的离线Tarjan算法_C++
这个Tarjan算法是求LCA的算法,不是那个强连通图的 它是 离线 算法,时间复杂度是 O(m+n),m 是询问数,n 是节点数 它的优点是比在线算法好写很多 不过有些题目是强制在线的,此类离线算法 ...
- LCA最近公共祖先 Tarjan离线算法
学习博客: http://noalgo.info/476.html 讲的很清楚! 对于一颗树,dfs遍历时,先向下遍历,并且用并查集维护当前节点和父节点的集合.这样如果关于当前节点(A)的关联节点( ...
- lca(最近公共祖先(离线))
转自大佬博客 : https://www.cnblogs.com/JVxie/p/4854719.html LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 首先是最近公共祖先 ...
- lca最近公共祖先与树上倍增。
https://vjudge.net/contest/295298#problem/A lca 的题目 求任意两点的距离. A题是在线算法,用st表rmq来实现. https://blog.csdn. ...
- LCA 近期公共祖先 小结
LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...
- lca 最近公共祖先
http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- LCA(最近公共祖先)模板
Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...
随机推荐
- css实现简单的告警提示动画效果
需求:css实现简单的告警提示动画效果,当接收到实时信息的时候,页面弹出告警信息的动画效果 <!DOCTYPE html> <html lang="en"> ...
- 【Codeforces Round #429 (Div. 1) B】Leha and another game about graph
[链接]点击打开链接 [题意] 给出一个连通图,并给每个点赋一个d值0或1或-1,要求选出一个边的集合,使得所有的点i要么d[i] == -1,要么 dgree[i] % 2 == d[i],dgr ...
- docker 第一课 —— 从容器到 docker
1. 容器的概念 一种虚拟化的解决方案 与虚拟机所不同的是,虚拟机通过中间层,将一台或多台独立的机器虚拟运行于物理硬件之上: 而容器是直接运行于操作系统内核之上的用户空间: 基于上述,容器虚拟化也被称 ...
- Java反射机制的简单应用
一直感觉java的反射机制非常强大,可是可用的地方不多.在android学习的时候.一直想实现挂断电话的功能,可是系统并没有提供开放的api接口,看了一下网上使用反射机制来实现该功能,确实非常强大,非 ...
- js私有变量
js私有变量 一.总结 1.在js函数中定义 this.name='张三'; (函数的属性)外部是可以访问的,但是 var name='张三'; (函数的私有变量),这样定义的话外部没有办法访问 2. ...
- nginx 代理服务器
目前现状:只有1个机器能上网(web),其他机器不能方法:能上网的做一个代理web服务器中转,其他机器连接它即可。采用nginxNginx配置如下:server{ resolver 8. ...
- C++基础学习教程(七)----类编写及类的两个特性解析--->多态&继承
类引入 到眼下为止我们所写的自己定义类型都是keywordstruct,从如今起我们将採用class方式定义类,这样的方式对于学习过其它高级语言包含脚本(Such as Python)的人来说再熟悉只 ...
- Xposed也要热更新
好久没写博客了.这次玩一点不一样的. 吐槽&起因 相信熟悉Xposed的小伙伴们都知道,每次写完Xposed都要重新启动啊.有木有!反射错了,写错了名字.改一个log,都要重新启动啊有木有!重 ...
- js进阶解决浏览器缓存不能自动更新的问题(在ajax的url上带上一个参数,可以是日期,或者是随机数)(随机数Math.random)(取得日期的毫秒数:new Date().getTime();)
js进阶解决浏览器缓存不能自动更新的问题(在ajax的url上带上一个参数,可以是日期,或者是随机数)(随机数Math.random)(取得日期的毫秒数:new Date().getTime();) ...
- js进阶ajax基本用法(创建对象,连接服务器,发送请求,获取服务器传过来的数据)
js进阶ajax基本用法(创建对象,连接服务器,发送请求,获取服务器传过来的数据) 一.总结 1.ajax的浏览器的window对象的XMLHtmlRequest对象的两个重要方法:open(),se ...