LCA

1.倍增法(在线做法)

首先预处理从任一节点出发依次到达节点的树深,以及到该节点的路径长度,分别用d[],dis[]表示。

f[i][j]表示i节点向着根节点走2^j步能到达的节点,若走2^j步后没有点则记为0,所以输入点时需要看题目有没有0点输入,如果有的话,给所有点都向后偏移一位。

i向根节点走2^k步可以分解为走2^(k-1)步,再走2^(k-1)步,那么可以得到递推公式:f[i][j]=f[ f[i][j-1] ][j-1];

故一次bfs预处理就可以处理出来f[][],dis[],d[]数组。

在之后寻找x与y的LCA,

  1. 首先选择较深的那个点,试着向根节点走,调整到与另一节点同一层次,那么这两个点要么处于同一个点,要么处以同一层次的不同点处;
  2. 若此时两点不同,则两点尝试同时向根节点走相同的步数,如果走相同步数后两点相同则不走,否则走
  3. 则最后x与y的上一层一定是最近的公共祖先了。

关于为什么第二步的走法,看图:若相同时向上走的话,会走到最顶端,而这个点不是LCA。

 

typedef long long ll;
const int inf=0x3f3f3f3f;

const int maxn=1e5+7;
int f[maxn][18];//f[i][j]表示从i向上翻2^j到达的节点;
int d[maxn];//树深;
ll dis[maxn];//该点距离1的距离;
int mi;//次幂;


struct Edge{
    int v,next;
    ll w;
}edge[maxn<<1];

int head[maxn],top;

void add(int u,int v,ll w){
    edge[top].v=v;
    edge[top].w=w;
    edge[top].next=head[u];
    head[u]=top++;
}

void init(){
    memset(head,-1,sizeof(head));
    top=0;
    memset(d,0,sizeof(d));
    memset(f,0,sizeof(f));
    memset(dis,0,sizeof(dis));
}

queue<int>q;

void bfs(){
    while(!q.empty()) q.pop();
    dis[1]=0;
    q.push(1);
    d[1]=1;
    int u,v;
    while(!q.empty()){
        u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=edge[i].next){
            v=edge[i].v;
            if(d[v]) continue;
            d[v]=d[u]+1;
            dis[v]=dis[u]+edge[i].w;
            f[v][0]=u;
            for(int j=1;j<=mi;++j)
                f[v][j]=f[f[v][j-1]][j-1];
            q.push(v);
        }
    }
}

int LCA(int x,int y){
    if(d[x]>d[y]) swap(x,y);//y比x深;
    for(int i=mi;i>=0;--i)
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(x==y) return x;
    for(int i=mi;i>=0;--i)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

ll getlen(int x,int y){
    return dis[x]+dis[y]-2*dis[LCA(x,y)];
}

给一道题:https://codeforces.com/gym/101808/problem/K

这个题给的是个n条边的图,那么先去掉一条边变成树之后跑就可以了。

2.tarjan算法(离线做法)以How far away ? HDU - 2586为例

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=4e4+7;

const int maxm=8e4+7;

struct Edge{
    int v,w,next;
}edge[maxm];

int head[maxn],top;

void init(){
    top=0;
    memset(head,-1,sizeof(head));
}

void add(int u,int v,int w){
    edge[top].v=v;
    edge[top].w=w;
    edge[top].next=head[u];
    head[u]=top++;
}

int fa[maxn],d[maxn],vis[maxn],lca[maxn],ans[maxn];
vector<int> query[maxn],query_id[maxn];

void add_query(int x,int y,int id){
    query[x].push_back(y),query_id[x].push_back(id);
    query[y].push_back(x),query_id[y].push_back(id);
}

int get(int x){
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}

void tarjan(int u){
    vis[u]=1;
    int v,w,id;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        w=edge[i].w;
        if(vis[v]) continue;
        d[v]=d[u]+w;
        tarjan(v);
        fa[v]=u;
    }
    for(int i=0;i<query[u].size();++i){
        v=query[u][i],id=query_id[u][i];
        if(vis[v]==2){
            int lca=get(v);
            ans[id]=min(ans[id],d[u]+d[v]-2*d[lca]);
        }
    }
    vis[u]=2;
}

int main(){
    int t,n,m,u,v,w;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        top=0;
        for(int i=1;i<=n;++i){
            head[i]=-1;
            fa[i]=i;
            vis[i]=0;
            query[i].clear(),query_id[i].clear();
        }

        for(int i=1;i<n;++i){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w),add(v,u,w);
        }
        for(int i=1;i<=m;++i){
            scanf("%d%d",&u,&v);
            if(u==v) ans[i]=0;
            else{
                add_query(u,v,i);
                ans[i]=1<<30;
            }
        }
        tarjan(1);
        for(int i=1;i<=m;++i) printf("%d\n",ans[i]);

        if(t) printf("\n");
    }

    return 0;
}

 

<think>首先,用户的问题是关于在线LCA(最近公共祖先)算法的实现。我需要参考系统级指令用户可见层的规则来构建回答。系统级指令:-所有行内数学表达式用$...$格式,例如:$x^2$。-独立公式用$$...$$格式并单独成段。-LaTeX语法必须正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识,例如:[^1]。用户可见层:-回答结构清晰,帮助用户逐步解决问题。-尽量保证回答真实可靠。-参考站内引用:引用[1]引用[2]是关于LCA的,但它们是示例引用,我需要基于真实知识回答。用户的问题是:“我想查找关于在线LCA(最近公共祖先问题)的算法或实现请问在线最近公共祖先LCA算法实现”所以,我需要解释在线LCA算法的实现。在线算法意味着程序可以逐个处理查询,而不需要所有输入一开始就给出。关键点:-在线LCA通常使用倍增法(BinaryLifting)实现。-这与引用[2]提到的相似:存储每个节点的第$2^j$个祖先。-引用[1]定义了在线算法:程序可以序列化处理输入。我的回答应该包括:1.解释什么是在线LCA算法。2.描述倍增法的实现步骤。3.提供伪代码或代码示例。4.添加引用标识(如果适用)。5.在末尾生成相关问题。结构回答:-先介绍在线LCA。-逐步解释算法。-给出实现代码(用代码块)。-最后生成相关问题。使用中文回答。数学表达式:-行内:例如,节点深度$depth[u]$。-独立公式:如果需要,比如公式。引用:-引用[1][2]是提供的,我可以自然地引用它们,因为它们相关。-例如,在解释倍增法时,引用[2]。实现细节:-倍增法:-预处理:存储每个节点的祖先数组。-查询:通过二进制提升找到LCA。-步骤:1.预处理树的深度祖先信息。2.对于每个查询(u,v),调整到相同深度,然后一起向上跳。伪代码:-初始化祖先数组。-查询函数。生成相关问题:-类似于示例,生成3-4个相关问题。完整回答框架:1.解释在线LCA:在线算法允许逐个处理查询,而不需要所有查询输入一开始就给出。2.描述倍增法实现:-预处理:DFS遍历树,计算深度直接父节点。-构建祖先表:对于每个节点每个j(0到max_depth),存储$anc[i][j]$表示节点i的第$2^j$个祖先。-查询:将两个节点升到相同深度,然后逐步向上跳。3.提供代码示例(用Python或类似语言)。4.引用:[^1]用于在线算法定义,[^2]用于倍增法实现。5.生成相关问题。确保LaTeX正确:-行内:$depth[u]$,$2^j$-独立:如果有公式,如$$\text{LCA}(u,v)=\text{...}$$,但可能不需要。代码块:使用```language```格式。最终,生成相关问题:例如:1.在线LCA算法的复杂度分析?2.如何优化倍增法?3.在线LCA的应用场景?</think>### 在线最近公共祖先(LCA)算法实现 在线最近公共祖先(LCA)算法用于在有根树中快速回答查询,即给定两个节点$u$$v$,找到它们最近的公共祖先。在线算法的核心特点是它可以逐个处理查询输入,而无需预先知道所有查询[^1]。这与离线算法(如Tarjan算法)不同,后者需要一次性加载所有查询。在线LCA的常用实现是**倍增法(Binary Lifting)**,它通过预处理树的祖先信息来高效支持查询。下面我将逐步解释算法原理并提供代码实现。 #### 算法原理 倍增法的核心思想是预处理每个节点的$2^j$级祖先(即节点向上跳$2^j$步的祖先),从而在查询时通过二进制提升快速调整节点深度并找到LCA。算法分为两个阶段: 1. **预处理阶段**: - 通过DFS遍历树,计算每个节点的深度$depth[u]$直接父节点(即$2^0$级祖先)。 - 构建一个祖先表$anc$,其中$anc[u][j]$存储节点$u$的第$2^j$个祖先。利用动态规划:$anc[u][j] = anc[ anc[u][j-1] ][j-1]$(当$j \geq 1$时)[^2]。 2. **查询阶段**: - 给定查询$(u, v)$,先将较深的节点向上跳到与另一个节点相同深度(使用二进制提升)。 - 然后,两个节点一起向上跳,直到它们的祖先相同。具体地,从最大$j$开始尝试跳跃,如果跳跃后祖先不同才执行跳跃。 算法的时间复杂度: - 预处理:$O(n \log n)$,其中$n$是节点数。 - 单次查询:$O(\log n)$。 空间复杂度:$O(n \log n)$。 #### 实现代码 以下是用Python实现的倍增法在线LCA算法。代码包括树的构建、预处理查询函数。假设树以邻接表形式存储,节点编号从1开始。 ```python import math from collections import defaultdict, deque class LCA: def __init__(self, n, root=1): self.n = n self.root = root self.graph = defaultdict(list) self.depth = [0] * (n + 1) self.anc = defaultdict(lambda: [0] * (self.max_j + 1)) # 祖先表 def add_edge(self, u, v): """添加树边""" self.graph[u].append(v) self.graph[v].append(u) def preprocess(self): """预处理:计算深度祖先表""" # 初始化最大跳跃指数 max_j = floor(log2(n)) self.max_j = math.floor(math.log2(self.n)) + 1 # BFS计算深度直接父节点 queue = deque([self.root]) self.depth[self.root] = 1 parent = [0] * (self.n + 1) # 直接父节点 while queue: u = queue.popleft() for v in self.graph[u]: if v == parent[u]: continue parent[v] = u self.depth[v] = self.depth[u] + 1 queue.append(v) # 构建祖先表 anc[u][j] for j in range(self.max_j + 1): for i in range(1, self.n + 1): if j == 0: self.anc[i][j] = parent[i] # 2^0 = 1级祖先 else: prev = self.anc[i][j - 1] self.anc[i][j] = self.anc[prev][j - 1] if prev != 0 else 0 def query(self, u, v): """在线查询LCA(u, v)""" # 确保u较深,否则交换 if self.depth[u] < self.depth[v]: u, v = v, u # 将u跳到与v相同深度 diff = self.depth[u] - self.depth[v] j = 0 while diff: if diff & 1: # 二进制位为1时跳跃 u = self.anc[u][j] diff //= 2 j += 1 # 如果此时uv相同,则v是LCA if u == v: return u # 一起向上跳,直到祖先相同 for j in range(self.max_j, -1, -1): if self.anc[u][j] != self.anc[v][j]: u = self.anc[u][j] v = self.anc[v][j] return self.anc[u][0] # LCA是u的直接父节点 # 示例用法 if __name__ == "__main__": # 构建树: 节点数n=5, 根节点为1 lca = LCA(n=5, root=1) edges = [(1, 2), (1, 3), (2, 4), (2, 5)] # 树边 for u, v in edges: lca.add_edge(u, v) lca.preprocess() # 预处理 # 在线查询示例 print(lca.query(4, 5)) # 输出: 2 (LCA of 4 and 5 is 2) print(lca.query(3, 4)) # 输出: 1 (LCA of 3 and 4 is 1) ``` #### 关键说明 - **在线处理**:此实现允许逐个添加查询(如`query(4, 5)`),无需预先知道所有查询,符合在线算法定义[^1]。 - **优化**:使用BFS代替DFS避免递归深度限制;二进制提升减少跳跃次数。 - **适用场景**:适用于动态查询环境,如实时网络路由或游戏AI中的路径查找。 如果您有特定树结构或查询序列,我可以帮助调整代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值