POJ 1986 Distance Queries(求最近公共祖先,LCA-Tarjan)

Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible! 
Input
* Lines 1..1+M: Same format as "Navigation Nightmare" 

* Line 2+M: A single integer, K. 1 <= K <= 10,000 

* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms. 
Output
* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance. 
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
Hint
Farms 2 and 6 are 20+3+13=36 apart. 

题目大意:John是一个农场主,他的牛很懒,拒绝按照John选的路走。John不得不找一条最短的路。这道题的输入前半部分和POJ1984"Navigation Nightmare"相同。在每组数据之后是一个整数K,接下来K行是询问(u,v)的曼哈顿距离(u,v是农场编号)。最后输出所有询问结果。

POJ1984链接:http://poj.org/problem?id=1984

思路:本题输入有些特殊,给出的是某点在某点的某个方向(东西南北)有多远。由于输入数据比较特殊。全部是有向边,且构不成回路,所以一定会是一棵树。  所以可以根据直接套用Tarjan-LCA算法的模板,考虑树上每对节点的最短路径计算。如果确定根为1,则有Dist(u,v) = Dist(1,u) + Dist(1,v) - 2*Dist( 1,LCA(u,v) )。先深搜一次遍历求出每个节点到根结点的距离,再做一次LCA,就可以得出结果了。


链式向前星(静态邻接表)算法详解:(经典!)深度理解链式前向星(静态邻接表)

最近公共祖先LCA(Tarjan算法)详解:LCA 最近公共祖先

#include<iostream>  
#include<algorithm>  
#include<cstdio>  
#include<cstring>  
using namespace std;  
const int MAXN = 80080;  
const int MAXQ = 20020;  
  
int father[MAXN],Head[MAXN],QHead[MAXN],Dist[MAXN];  
//father[i]代表第i个节点的父节点
//Head[i]代表以i为起点的(反向)第一条边存储的位置,实际这里的第一条边存储的位置其实在以i为起点的所有边的最后输入的那个编号.  
//QHead[i]代表第二次输入要查询的边,也是代表以i为起点的(反向)第一条边存储的位置
//Dist[i]代表i到总根节点的长度
struct EdgeNode  
{  
    int to;  
    int next;  
    int lca;  
}Edges[MAXN];  //Edges[i]存储前半部分输入数据
            //其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].lca为边权值
EdgeNode QEdges[MAXN];  //QEdges[i]存储后半部分输入数据
            //其中Qedge[i].lca为最近公共祖先的值
int find(int x)  //寻找x的父亲节点,例如find(9)的顺序为f[9]=7-->f[7]=5-->f[5]=5 return 5;
{  
    if(x != father[x])  
        father[x] = find(father[x]);  
    return father[x];  
}  
  
bool vis[MAXN];  //vis[i]表示i点是否被访问过
  
void LCA(int u)  //顺序:1搜本身 2搜子节点(访问过了则跳过) 3搜有关系的(没访问则跳过)
{  
    father[u] = u;  
    vis[u] = true;  
    for(int k = Head[u]; k != -1; k = Edges[k].next)  //DFS
    {  
        if(!vis[Edges[k].to])  //若没访问过
        {  
            Dist[Edges[k].to] = Dist[u] + Edges[k].lca;//u的子节点Edges[k].to到根节点的总权值=u到它的子节点(Edges[k].to)的权值+Dist[u]  
            LCA(Edges[k].to);  //递归,继续求u的子节点
            father[Edges[k].to] = u;  //u的子节点Edges[k].to的父节点设为u
        }  
    }  
  
    for(int k = QHead[u]; k != -1; k = QEdges[k].next)  
    {  
        if(vis[QEdges[k].to])  //若访问过了
        {  
            //QEdges[k].lca = find(QEdges[k].to);  
            QEdges[k].lca = Dist[u] + Dist[QEdges[k].to] - 2*Dist[find(QEdges[k].to)];  
            QEdges[k^1].lca = QEdges[k].lca;  //k^1指的是若k为奇数,则k^1=偶数,若为偶数,则k^1为奇数。
            //假如此时查询的是a,b的LCA,则QEdges[k].lca存储a->b的最近公共祖先,QEdges[k^1].lca存储b->a的最近公共祖先
        }  
    }  
}  
  
int main()  
{  
    int N,M,K,u,v,w,a,b;  
    char s;  
    while(~scanf("%d%d",&N,&M))  
    {  
        memset(father,0,sizeof(father));  
        memset(Head,-1,sizeof(Head));  //Head都初始化为-1
        memset(QHead,-1,sizeof(QHead));  
        memset(vis,false,sizeof(vis));  
        memset(Edges,0,sizeof(Edges));  
        memset(QEdges,0,sizeof(QEdges));  
        memset(Dist,0,sizeof(Dist));  
        int id = 0;  
        for(int i = 0; i < M; ++i)  //输入前半部分,保存在Edges[i]和Head[i]里面
        {  
            scanf("%d%d%d %c",&u,&v,&w,&s);  
            Edges[id].to = v;  
            Edges[id].lca = w;  
            Edges[id].next = Head[u];  
            Head[u] = id++;  
            Edges[id].to = u;  
            Edges[id].lca = w;  
            Edges[id].next = Head[v];  
            Head[v] = id++;  
        }  
        scanf("%d",&K);  
        int iq = 0;  
        for(int i = 0; i < K; ++i)  //输入后半部分,保存在QEdges[i]和QHead[i]里面
        {  
            scanf("%d%d",&a,&b);  
            QEdges[iq].to = b;  
            QEdges[iq].next = QHead[a];  
            QHead[a] = iq++;  
            QEdges[iq].to = a;  
            QEdges[iq].next = QHead[b];  
            QHead[b] = iq++;  
        }  
        LCA(1);  //从根节点1开始深搜
        for(int i = 0; i < iq; i+=2)//i每次加2,因为一个输入查询占两个位置  
            printf("%d\n",QEdges[i].lca);  
    }  
  
    return 0;  
} 




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值