【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 ...
随机推荐
- 【几何/数学】概念的理解 —— (非)刚体变换((non-)rigid transformation)
1. 刚体变换与非刚体变换 What is a non-rigid transformation? 刚体变换(rigid transformation)一般分为如下几种: 平移对象,而不改变形状和大小 ...
- ssh远程执行命令并自动退出
usage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [ ...
- struts2笔记---struts2的执行过程
1.服务器启动: 加载项目web.xml 创建struts核心过滤器对象,执行filter-->init() struts-default.xml 核心功能的初始化 struts-plu ...
- 一次性能优化将filter转换
有一条SQL性能有问题,在运行计划中发现filter.遇到它要小心了,类似于nestloop.我曾经的blog对它有研究探索运行计划中filter的原理.用exists极易引起filter. 优化前: ...
- 20、RTC驱动程序
drivers\rtc\rtc-s3c.c s3c_rtc_init platform_driver_register s3c_rtc_probe rtc_device_register(" ...
- [RxJS] Use takeUntil instead of manually unsubscribing from Observables
Manually unsubscribing from subscriptions is safe, but tedious and error-prone. This lesson will tea ...
- 关于stm32的RAM大小
- Spring的任务调度@Scheduled注解——task:scheduler和task:executor的解析
原文地址: https://blog.csdn.net/yx0628/article/details/80873774 一个简单的Spring定时任务的 demo,全部代码见下载地址:https:// ...
- 几种常见web 容器比较 (tomcat、 jboss 、resin、 weblogic、 websphere、 glassfish)(转)
点击打开PDF下载链接 web 容器比较 tomcat jboss resin weblogic websphere glassfish 1. Tomcat是Apache鼎力支持的Java Web应用 ...
- openstack中虚拟机怎么与物理机通信
How-to-connection-ns-outside 环境配置 网络接口 vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 TYPE ...