LCA
LCA是用来求解一棵树或图中两点的最近公共祖先
LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。
1:如果读入的树,那么应该有固定的根节点我们只要对根节点LCA即可。
2:如果读入的是无向图,那么选择1作为根节点进行LCA即可。
所以应该要注意题目读入的是树还是无向图,已经的根节点是否固定。
1Tarjan离线算法
1:Tarjan算法基于深度优先搜索的框架,对于新搜索到的一个结点,首先创建由这个结点构成的集合,用到dfs+并查集
2:再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
3:其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
4:之后继续搜索下一棵子树,直到当前结点的所有子树搜索完。
5:这时把当前结点也设为已被检查过的,同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。
举例说明(非证明):
假设遍历完10的孩子,要处理关于10的请求了
取根节点到当前正在遍历的节点的路径为关键路径,即1-3-8-10
集合的祖先便是关键路径上距离集合最近的点
比如此时:
1,2,5,6为一个集合,祖先为1,集合中点和10的LCA为1
3,7为一个集合,祖先为3,集合中点和10的LCA为3
8,9,11为一个集合,祖先为8,集合中点和10的LCA为8
10,12为一个集合,祖先为10,集合中点和10的LCA为10
你看,集合的祖先便是LCA吧,所以第3步是正确的
道理很简单,LCA(u,v)便是根至u的路径上到节点v最近的点
6时间复杂度o(n+m);n是数据规模,m是询问的次数,核心如下
/*这个u是根节点*/
void LCA(int u){
ancestor[u] = u;/*建立一个集合*/
for(int i = first[u] ; i != -1 ; i = next[i]){
LCA(i);
union_Set(i , u);
ancestor[find_Set(i)] = u;
}
vis[u] = 1;/*把这个点标记为已经求过和它有关的LCA问题都可以知道*/
for(int i = 0 ; i < v[u].size() ; i++){
if(vis[v[u][i]]){
int tmp = father[find_Set(v[u][i])];
for(int j = 0 ; j < m ; j++){/*找到是那一条边询问*/
if(e[j].x == u && e[j].y == v[u][i] || e[j].y == u && e[j].x == v[u][i])
e[j].ans = tmp;
}
}
}
}