4.8 LCA

LCA

即最近公共祖先,在x,y的公共祖先中,深度最大的被称为最近公共祖先

1. 基础算法

1. 倍增法

在线算法,可以单独处理每个查询

const int N=500005;
struct Edge{int to, next;}edge[2*N];     //链式前向星
int head[2*N], cnt;
void init(){                             //链式前向星:初始化
    for(int i=0;i<2*N;++i){ edge[i].next = -1;   head[i] = -1; }
    cnt = 0;
}
void addedge(int u,int v){               //链式前向星:加边
	edge[cnt].to = v;  edge[cnt].next = head[u];  head[u] = cnt++;
} //以上是链式前向星
int fa[N][20], deep[N];
void dfs(int x,int father){        //求x的深度deep[x]和fa[x][]。father是x的父结点。
    deep[x] = deep[father]+1;      //深度:比父结点深度多1
    fa[x][0] = father;             //记录父结点
    for(int i=1; (1<<i) <= deep[x]; i++)    //求fa[][]数组,它最多到根结点
    	    fa[x][i] = fa[fa[x][i-1]][i-1];
    for(int i=head[x]; ~i; i=edge[i].next)  //遍历结点i的所有孩子。~i可写为i!=-1
        if(edge[i].to != father)            //邻居:除了父亲,都是孩子
           dfs(edge[i].to, x);
}
int LCA(int x,int y){
    if(deep[x]<deep[y])  swap(x,y);  //让x位于更底层,即x的深度值更大
    //(1)把x和y提到相同的深度
    for(int i=19;i>=0;i--)           //x最多跳19次:2^19 > 500005
        if(deep[x]-(1<<i)>=deep[y])  //如果x跳过头了就换个小的i重跳
            x = fa[x][i];            //如果x还没跳到y的层,就更新x继续跳
    if(x==y)  return x;              //y就是x的祖先
    //(2)x和y同步往上跳,找到LCA
    for(int i=19;i>=0;i--)           //如果祖先相等,说明跳过头了,换个小的i重跳
        if(fa[x][i]!=fa[y][i]){      //如果祖先不等,就更新x、y继续跳
            x = fa[x][i];   y = fa[y][i];
        }
    return fa[x][0];                 //最后x位于LCA的下一层,父结点fa[x][0]就是LCA
}
int main(){    
    init();                          //初始化链式前向星
    int n,m,root;  scanf("%d%d%d",&n,&m,&root); 
    for(int i=1;i<n;i++){            //读一棵树,用链式前向星存储
        int u,v;       scanf("%d%d",&u,&v); 
        addedge(u,v);  addedge(v,u);
    }
    dfs(root,0);                      //计算每个结点的深度并预处理fa[][]数组
    while(m--){
        int a,b;   scanf("%d%d",&a,&b); 
        printf("%d\n", LCA(a,b));
    }
    return 0;
}
2. Tarjan算法

离线算法,对查询进行排序后再计算,可以得到很好的效率

const int N=500005;
int fa[N], head[N], cnt, head_query[N], cnt_query, ans[N];
bool vis[N];
struct Edge{ int to, next, num;}edge[2*N], query[2*N];  //链式前向星
void init(){              //链式前向星:初始化
    for(int i=0;i<2*N;++i){
        edge[i].next = -1;  head[i] = -1;
        query[i].next = -1; head_query[i] = -1;
    }
    cnt = 0; cnt_query = 0;
}
void addedge(int u,int v){             //链式前向星:加边
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void add_query(int x, int y, int num) { //num 第几个查询
query[cnt_query].to = y;
query[cnt_query].num = num;	   //第几个查询
query[cnt_query].next = head_query[x];
head_query[x] = cnt_query++;
}
int find_set(int x) {	                //并查集查询
return fa[x] == x ? x : find_set(fa[x]);
}
void tarjan(int x){                    //tarjan是一个DFS
vis[x] = true;
for(int i=head[x]; ~i; i=edge[i].next){   // ~i可以写为i!=-1
int y = edge[i].to;
if( !vis[y] ) {     //遍历子结点
tarjan(y);
fa[y] = x;      //合并并查集:把子结点y合并到父结点x上
		}
	}
	for(int i = head_query[x]; ~i; i = query[i].next){ //查询所有和x有询问关系的y
	    int y = query[i].to;
	    if( vis[y])                                   //如果to被访问过
	        ans[query[i].num] = find_set(y);          //LCA就是find(y)
	}
}
int main () {
    init();
memset(vis, 0, sizeof(vis));
int n,m,root;  scanf("%d%d%d",&n,&m,&root);
for(int i=1;i<n;i++){                //读n个结点
fa[i] = i;                       //并查集初始化
int u,v;   scanf("%d%d",&u,&v);
        addedge(u,v);  addedge(v,u);     //存边
}
fa[n] = n;                           //并查集的结点n
for(int i = 1; i <= m; ++i) {        //读m个询问
int a, b; scanf("%d%d",&a,&b);
add_query(a, b, i); add_query(b, a, i);  //存查询
}
tarjan(root);
for(int i = 1; i <= m; ++i)	printf("%d\n",ans[i]);
}

2. 树上两点之间的最短距离

两点深度之和减去两倍的LCA深度

d i s t ( x , y ) = d e e p [ x ] + d e e p [ y ] − 2 ∗ d e p p [ L C A ( x , y ) ] dist(x,y)=deep[x]+deep[y]-2*depp[LCA(x,y)] dist(x,y)=deep[x]+deep[y]2depp[LCA(x,y)]

3. 树上差分

树上两点 u − > v u->v u>v可看做 u − > L C A ( x , y ) u->LCA(x,y) u>LCA(x,y) v − > L C A ( x , y ) v->LCA(x,y) v>LCA(x,y),设x为L,LCA(x,y)的父节点为R+1,D[L]++,D[R+1]–,对另一条线路同理

//洛谷P3128,LCA + 树上差分
#include <bits/stdc++.h>
using namespace std;
#define N 50010
struct Edge{int to,next;}edge[2*N]; //链式前向星
int head[2*N],D[N],deep[N],fa[N][20],ans,cnt;
void init();                      
void addedge(int u,int v); 
void dfs1(int x,int father);       
int LCA(int x,int y); //以上4个函数和“树上的倍增”中洛谷P3379的倍增代码完全一样
void dfs2(int u,int fath){
	for (int i=head[u];~i;i=edge[i].next){   //遍历结点i的所有孩子。~i可以写为i!=-1
		int e=edge[i].to;
		if (e==fath) continue;
		dfs2(e,u);
		D[u]+=D[e];
	}
	Ans = max(ans,D[u]);
}
int main(){
    init();        //链式前向星初始化
int n,m;  scanf("%d%d",&n,&m);
for (int i=1;i<n;++i){
         int u,v; scanf("%d%d",&u,&v);
         addedge(u,v); addedge(v,u);
	}
dfs1(1,0);     //计算每个结点的深度并预处理fa[][]数组
for (int i=1; i<=m; ++i){
         int a,b; scanf("%d%d",&a,&b);
         int lca = LCA(a,b);
         D[a]++;  D[b]++;  D[lca]--;  D[fa[lca][0]]--;    //树上差分
	} 
	dfs2(1,0);     //用差分数组求每个结点的权值
	printf("%d\n",ans);
	return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值