Distance Queries POJ - 1986 (LCA在线方法求解 公共祖先(带权值))

                    **Distance Queries** 

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.

先简单说一下题的大意和 解题思路:这道题是 求公共祖先的升级版,带有权值,题目要求你求解 两个节点之间的最小路程,那其实就是求解 两个节点到他们最近公共祖先的距离的和。好啦 题的大意 已经说完啦,其实题意不难理解,下面说一下,大概的解题过程: 这道题是在求解最近公共祖先的基础上进行的, 那么 首先要做的 就是 ,用邻接表 存储数据,然后定义 一个存储 路径的数组 和 一个存储 与路径对应深度的数组, 还有一个 数组用来存储 节点第一次在路径中出现的位置,另外,还要用一个 结构体 存储(数组应该也可以)节点到根节点的距离(此题的根结点不用寻找,直接把一个节点当作根节点即可,对所求的答案不影响,画个图 理解一下就懂啦),那么 过程 就可以简单的写作 : 邻接表 + LAC + RMQ + 公式(dist(root, x) + dist(root, y) - 2 * dist(root, xy的公共祖先)), 还有什么不懂得可以看看下面的代码,注释还是比较 详细的。

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
const int maxn = 100000;

//LCA
int index[maxn*2]; //DFS遍历到的结点数组
int dep[maxn*2];   //DFS遍历到的结点的深度的数组
int first[maxn];  //各结点在结点数组第一次现在时的在结点下标

int total; //index/dep数组的全局下标

///无向图的DFS
int N,M;
int root;

struct Edge
{
    int v;
    int len;

    Edge* link;
};

struct Node
{
    int dis; // 根结点到结点u的距离

    Edge* link;
};

Node node[maxn];  //用邻接链表存放图, 这个不好理解存储的过程
Edge buffer[maxn*2];
int cnt = 0;

///

void add_edge(int u,int v,int w)
{
    //插入边<u,v>
    Edge* tmp_edge = &buffer[cnt++];

    tmp_edge->v = v;
    tmp_edge->len = w;
    tmp_edge->link = node[u].link;

    node[u].link = tmp_edge;
}


bool vis[maxn];

void DFS(int id,int DEP,int dis)   //(2E)
{
    //
    vis[id] = 1;
    node[id].dis = dis;                                 // 根结点到结点u的距离

    //LCA
    total++;                                            // total  在这里 相当于 时间,无论在 遍历深入过程中 还是 回溯过程中 都会不断累加 从而充当记录数组的下标
    index[total] = id;                                  // 这里 是用来 记录 在遍历 不断深入的过程中 节点 和 对应的深度的记录  
    dep[total] = DEP;

    if(first[id] == 0)                                   // 记录节点 在遍历中 第一次出现的位置
        first[id] = total;
    //
    Edge* tmp_edge = node[id].link;                     // 定义 一个临时变量,存储 下一个链表对应的数据

    while(tmp_edge)
    {
        if(vis[tmp_edge->v] == 0)                       // 因为 在用邻接表存储数据的过程中 ,(u, v)与(v, U) 存储了两次可能会有 数据重复所以  用数组标记一下 深入的路径
        {
            DFS(tmp_edge->v,DEP+1,dis+tmp_edge->len);   // 深入过程中 节点向下更新 深度+1  与根节点的距离 累加上 当前的节点到下一个节点的距离,

            //LCA
            total++;
            index[total] = id;                          // 这里是 记录 回溯 过程 回溯到的节点 和 对应的深度的记录
            dep[total] = DEP;
        }

        tmp_edge = tmp_edge->link;                       // 树的 一边 不断深入下去,然后回溯,然后 另一条边
    }
}

//RMQ
int dp[maxn][17];                                        //存放区间最小值在dep数组的位置(下标)

void RMQ_PRE(int *dep,int n)                            //NlogN
{
    for(int i=1; i<=n; i++)
        dp[i][0] = i;

    int m = log10(double(n)) / log10(2.0);

    for(int j=1; j<=m; j++)
        for(int i=1; i<=n && i+(1<<j)-1 <= n; i++)
        {
            if(dep[ dp[i][j-1] ] <= dep[ dp[i+(1<<(j-1))][j-1] ])

                dp[i][j] = dp[i][j-1];
            else
                dp[i][j] = dp[i+(1<<(j-1))][j-1];
        }
}

int RMQ(int *dep,int l,int r)                           //返回区间最小值在dep数组的位置(下标)
{
    int k = log(double(r-l+1)) / log(2.0);

    if(dep[ dp[l][k]]  <= dep[ dp[r-(1<<k)+1][k] ])

        return dp[l][k];
    else
        return dp[r-(1<<k)+1][k];
}

int main()
{
    scanf("%d%d",&N,&M);
    int u,v,w;
    char dir;

    for(int i=1; i<=N; i++)
    {
        vis[i] = 0;
    }

    memset(node,0,sizeof(node));

    while(M--)
    {
        scanf("%d%d%d %c",&u,&v,&w,&dir);

        add_edge(u,v,w);
        add_edge(v,u,w);
    }

    root = 1;// 直接就 把  ‘1’ 这个节点 当作 根节点 不懂得 自己各异找个 二叉树 画一下  任意一个 节点都可以当成根节点,这并不影响 节点之间的最小距离

    total = 0;
    memset(first,0,sizeof(first));

    DFS(root,0,0);

    RMQ_PRE(dep,2*N-1);

    int Q;
    scanf("%d",&Q);

    while(Q--)
    {
        scanf("%d%d",&u,&v);

        if(first[u] > first[v])                                      // 节点 出现的先后顺序不确定 
            swap(u,v);

        int par = index[ RMQ(dep,first[u],first[v]) ];               //得到最近公共祖先的序号

        printf("%d\n",node[u].dis + node[v].dis - 2 * node[par].dis);// 所有点 到 根节点的距离找到之后 直接带公式即可

    }

    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值