POJ 1986 Distance Queries 【RMQ LCA】双向存边

本文详细介绍了一种求解两点间最短路径的LCA算法,并提供了完整的AC代码实现。文章通过实例解释了如何利用dis数组存储节点到根节点的距离,并结合LCA模板输出两节点间的最短距离。

题目链接

题目大意:求节点a到节点b的最短距离


思路:LCA模版,用dis数组存节点root到根节点的距离,输出 dis[a] + dis[b] - 2*dis[ LCA(a,b) ];


WA坑点:vector要双向存边,不然会WA,虽然不知道为什么要双向存边,记住就好


AC代码:

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <vector>
#include <string.h>
#define N 100100

using namespace std;

struct node
{
    int x,len;
};
vector<node> vec[N];
int depth[2*N],id[N],vs[2*N],k,dp[2*N][30];
long long dis[2*N];

int MIN(int a, int b)
{
    if(depth[a] <= depth[b])
        return a;
    return b;
}

void RMQ(int n)
{
    for(int i = 1; i <= n; i++)
        dp[i][0] = i;
    for(int j = 1; (1<<j) <= n; j++)
        for(int i = 1; i+(1<<j)-1 <= n; i++)
            dp[i][j] = MIN(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}

void dfs(int root, int d, int len,int f)
{
    id[root] = k;
    dis[k] = len;
    vs[k] = root;
    depth[k++] = d;
    for(int i = 0; i < vec[root].size(); i++)
    {
        if(vec[root][i].x == f)
            continue;
        dfs(vec[root][i].x, d+1, len+vec[root][i].len,root);
        vs[k] = root;
        dis[k] = len;
        depth[k++] = d;
    }
}

int query(int l, int r)
{
    if(l > r)
        swap(l,r);
    int k = (int)(log10(r-l+1)/log10(2));
    return MIN(dp[l][k], dp[r-(1<<k)+1][k]);
}

int main()
{
    int n,m,a,b,l,in[N],q;
    char ch;
    while(cin>>n>>m)
    {
        for(int i = 0; i < N; i++)
            vec[i].clear();
        memset(in,0,sizeof(in));
        memset(dis,0,sizeof(dis));

        while(m--)
        {
            scanf("%d%d%d %c",&a, &b, &l, &ch);
            node x = {b,l}, y = {a,l};
            vec[a].push_back(x);
            vec[b].push_back(y);
            in[b] = 1;
        }

        int i = 1;
        while(in[i])
            i++;
        k = 1;
        dfs(i,0,0,-1);
        RMQ(2*n-1);

        cin>>q;
        while(q--)
        {
            scanf("%d%d",&a, &b);
            int fi = query(id[a],id[b]);
            printf("%lld\n",dis[id[a]] - 2*dis[fi] + dis[id[b]]);
        }
    }
    return 0;
}


<think>好的,我现在要解决用户关于POJ1986 Distance Queries的问题。首先,我需要确认这个题目的具体内容和要求。用户可能是在准备算法竞赛或者学习数据结构,需要了解这个问题的解决方法。 POJ1986的题目名称是Distance Queries,应该属于最近公共祖先(LCA)或树上的路径查询问题。这类问题通常需要预处理树结构,以便快速回答两点之间的距离。用户可能需要知道如何高效处理多次查询。 接下来,我需要回忆或查找POJ1986的具体描述。根据经验,这类问题通常是给定一棵无向树,多次询问两点之间的最短距离。因为树中任意两点路径唯一,所以距离可以通过LCA来计算。距离公式一般是dist(u, v) = dist(root, u) + dist(root, v) - 2 * dist(root, lca(u, v))。 接下来,我需要考虑如何实现。通常有两种方法:Tarjan的离线算法和倍增法的在线算法。Tarjan算法在处理所有查询时效率很高,但需要提前知道所有查询点。而倍增法则允许在线处理,每次查询的时间复杂度是O(log n),适合处理大量随机查询。 然后,思考具体步骤。首先,构建树的邻接表,记录每个节点的子节点和的权值。接着,预处理每个节点的深度和到根节点的距离。对于倍增法,还需要预处理每个节点向上2^k步的祖先节点。这一步通常通过DFS或BFS完成。 预处理完成后,对于每个查询(u, v),找到它们的LCA,然后利用之前计算的到根节点的距离来计算两点间的路径长度。这一步需要确保LCA查找的正确性,否则计算结果会出错。 可能需要注意的细节包括树的根节点选择(通常选1号节点),以及处理输入数据时可能有无关的方向信息,但题目可能给出的是无向树,因此邻接表需要双向添加,但遍历时要记录父节点避免回溯。 另外,输入数据中可能有冗余信息,比如题目中的“路”和“方向”,但实际可以忽略方向,因为树是无向的。需要提醒用户注意处理输入时只需提取有效信息。 测试样例的处理也很重要,例如当u和v相同时,距离应为0;当其中一个是另一个的祖先时,计算是否准确。需要确保代码能处理这些界情况。 最后,总结解决方法,给出算法步骤和复杂度分析。时间复杂度主要是预处理O(n log n)和每次查询O(log n),空间复杂度O(n log n)用于储倍增表。这样的复杂度对于n到4e4和查询数到1e4的情况应该是可以接受的。 用户可能需要具体的代码实现,但根据要求,不需要提供代码,而是解释思路。所以应该分步骤说明解决过程,确保用户理解如何应用LCA和距离计算的方法。 可能遇到的常见错误包括忘记处理双向导致死循环,或者预处理时没有正确计算每个节点的深度和距离。需要提醒用户注意这些点。 总结来说,回答应该包括问题分析、解决方法(LCA的应用)、算法步骤、复杂度说明以及可能的注意事项,帮助用户全面理解如何解决POJ1986的问题。</think>### POJ1986 Distance Queries 问题详解 #### 问题背景 题目要求计算树结构中多个节点对之间的最短路径长度。给定一棵包含 $N$ 个节点(节点编号从 $1$ 开始)的无向树,每条带有权值。需回答 $M$ 次查询,每次给出两个节点 $u$ 和 $v$,输出它们之间的最短距离。 #### 核心思路:LCA + 前缀和 1. **树的性质**:树中任意两节点间有唯一路径,最短距离即路径权之和。 2. **LCA(最近公共祖先)**:路径必经两节点的最近公共祖先。设 $dist[u]$ 表示根节点到 $u$ 的路径长度,则两点间距离公式为: $$dist(u, v) = dist[u] + dist[v] - 2 \cdot dist[LCA(u, v)]$$ 3. **倍增法求LCA**:通过预处理每个节点向上跳 $2^k$ 步的祖先,实现 $O(\log n)$ 时间复杂度的查询。 --- #### 解决步骤 **1. 树的储与预处理** - **邻接表树**:记录每个节点的相邻节点及权。 - **DFS/BFS预处理**: - 计算每个节点的深度 $depth[u]$ 和到根节点距离 $dist[u]$。 - 构建倍增表 $up[u][k]$,表示节点 $u$ 向上跳 $2^k$ 步的祖先。 **2. LCA查询实现** - **调整深度对齐**:将较深节点上跳至与另一节点同深度。 - **同步上跳找LCA**:从最大步长开始尝试,若祖先不同则上跳,直至找到共同祖先。 **3. 距离计算** - 根据公式直接计算,无需实际遍历路径。 --- #### 复杂度分析 - **预处理**:DFS/BFS遍历 $O(N)$,倍增表构建 $O(N \log N)$。 - **单次查询**:$O(\log N)$。 - **总复杂度**:$O(N \log N + M \log N)$,适用于 $N \leq 4 \times 10^4$ 和 $M \leq 10^4$ 的规模。 --- #### 关键实现细节 1. **根节点选择**:通常选择 $1$ 号节点为根,需保证树连通。 2. **输入处理**:忽略无关的方向信息,按无向构建邻接表。 3. **倍增表初始化**: - $up[u][0]$ 直接父节点。 - 递推公式:$up[u][k] = up[ up[u][k-1] ][k-1]$。 4. **界条件**: - 查询的两个节点相同时,距离为 $0$。 - 节点为对方祖先时需正确触发上跳终止条件。 --- #### 示例说明 **输入**: ``` 7 6 1 6 13 E 6 3 9 E 3 5 7 S 4 1 3 N 2 4 20 W 4 7 2 S 3 1 6 1 4 2 6 ``` **处理过程**: 1. 建树后,根为 $1$,计算各节点 $dist$ 值。 2. 查询 $2$ 和 $6$: - LCA(2,6) 为 $4$。 - 距离 = $dist[2] + dist[6] - 2 \cdot dist[4] = 3 + 13 - 2 \cdot 3 = 16$。 --- #### 总结 POJ1986 是经典的LCA应用问题,通过结合倍增法和前缀和,能够高效处理树上的路径查询。需注意树的储、预处理细节及界条件,代码实现时可选择DFS或BFS进行初始化。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值