POJ 1986 Distance Queries(离线tarjan-LCA)

本文深入探讨了树形结构中两点间距离的计算方法,通过离线Tarjan算法解决复杂查询问题,重点阐述了离线查询、LCA(最近公共祖先)求解及树上距离计算策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description
给出一棵节点数为n的树,q次查询,每次查询两点间距离
Input
第一行为两整数n和m分别表示点数和边数,之后m行每行三个整数a,b,c表示a和b之间有一条权值为c的边,之后一个字符表示这条边的方向,之后为一整数q表示查询次数,最后q行每行两个整数a和b表示查询点a到点b的距离(1<=q<=10000)
Output
对于每次查询,输出查询结果
Sample Input
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
Sample Output
13
3
36
Solution
树上两点距离dis(i,j)=dis(root,i)+dis(root,j)-2*dis(root,lca(i,j)),所以问题变成两部分,一个是求两点lca,另一个是求树上每点到根节点的距离,其中第一部分由于查询次数比较大所以可以用离线tarjan求lca,而第二部分可以在求lca的过程中一并处理
Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define maxn 111111
#define maxq 111111
struct Edge
{
    int to,next,c;
}edge[maxn*2];
struct Query
{
    int to,next,id;
}query[maxq*2];
int f[maxn];
bool vis[maxn];
int ancestor[maxn];
int head1[maxn],tot1;
int head2[maxq],tot2;
int ans[maxq]; 
int dis[maxn];
void init()
{
    tot1=tot2=0;
    memset(head1,-1,sizeof(head1));
    memset(head2,-1,sizeof(head2));
    memset(vis,0,sizeof(vis));
    memset(f,-1,sizeof(f));
    memset(ancestor,0,sizeof(ancestor));
}
int find(int x)
{
    if(f[x]==-1)return x;
    return f[x]=find(f[x]);
}
void unite(int x,int y)
{
    x=find(x),y=find(y);
    if(x!=y)f[x]=y;
}
void add_edge(int u,int v,int c)
{
    edge[tot1].to=v;
    edge[tot1].c=c;
    edge[tot1].next=head1[u];
    head1[u]=tot1++;
}
void add_query(int u,int v,int id)
{
    query[tot2].to=v;
    query[tot2].next=head2[u];
    query[tot2].id=id;
    head2[u]=tot2++;
    query[tot2].to=u;
    query[tot2].next=head2[v];
    query[tot2].id=id;
    head2[v]=tot2++;
}
void tarjan(int u)
{
    ancestor[u]=u;
    vis[u]=1;
    for(int i=head1[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(vis[v])continue;
        dis[v]=dis[u]+edge[i].c;
        tarjan(v);
        unite(u,v);
        ancestor[find(u)]=u;
    }
    for(int i=head2[u];~i;i=query[i].next)
    {
        int v=query[i].to;
        if(vis[v])ans[query[i].id]=dis[u]+dis[v]-2*dis[ancestor[find(v)]];
    }
} 
int main()
{
    int n,m,q;
    while(~scanf("%d%d",&n,&m))
    {
        int u,v,c;char s[3];
        init();
        while(m--)
        {
            scanf("%d%d%d%s",&u,&v,&c,s);
            add_edge(u,v,c),add_edge(v,u,c);
        }
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d",&u,&v);
            add_query(u,v,i);
        }
        dis[1]=0;
        tarjan(1);
        for(int i=1;i<=q;i++)
            printf("%d\n",ans[i]);
    }
    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进行初始化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值