谭松松的旅游计划 【LCA 求最短路】

本文介绍了一种利用最近公共祖先(LCA)算法解决特定场景下的最短路径问题的方法。通过建立一棵树形结构,并利用LCA算法,能够有效地找到两点间的最短路径,适用于大规模数据集。

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

谭松松是一个爱旅游的人,他非常的热爱旅游,即便身体被掏空也要坚持旅游。喵蛤王国由N个城市组成,由N-1条道路连通,每条道路长度为ci,使得任意两座城市都能相互到达。谭松松有M个旅游计划,每个旅游计划有一个起点ai,一个终点bi,所以谭松松想知道,对于每个旅游计划,从起点到终点的最短路长度为多少?

Input
第一行两个整数N(2≤N≤100000,),M(1≤M≤100000),表示城市个数,谭松松的旅游计划个数。

接下来N-1行,每行三个整数li,ri,ci(1≤ci≤10000),表示li号城市和ri号城市之间有一条长度为ci的道路。

接下来M行,每行两个整数ai,bi表示旅游计划的起点和终点。

Output
输出M行,表示每个旅游计划的最短距离。

Sample Input
7 6
1 2 5
1 3 4
3 7 7
2 4 4
2 5 3
5 6 2
1 2
4 7
6 3
3 2
5 4
7 6
Sample Output
5
20
14
9
7
21
Hint

题解: 首先一看n的大小,就知道没办法用DJK或者别的求最短路的算法,
学习了— 可以用LCA (最近公共祖先来求解最短路)
LCA 就是自己定义一个根节点建树

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#define LL long long
#define M  100000+100
using namespace std;
void read(int &x){
    x=0;char c;
    while((c=getchar())<'0');
    do x=x*10+c-'0';while((c=getchar())>='0');
}
struct Edge{
    int to,cost;
};
vector<Edge>G[M];
int pre[M];int n,m,q;
int ranks[M],value[M];
void getmap()
{
    int i,j;int a,b,c; 
    while(m--)
    {
        read(a);read(b);read(c);    
        G[a].push_back({b,c});
        G[b].push_back({a,c});
    }
}
queue<int>Q;
void bfs(int st)  //  用bfs 跑出 每个子节点的深度和每个点的父节点
{  //  这里也是可以用 dfs来跑 
    while(!Q.empty()) Q.pop();
//  memset(vis,0,sizeof(vis));
    //memset(pre,-1,sizeof(pre));
    memset(ranks,-1,sizeof(ranks));
    value[st]=0;ranks[st]=0;
    Q.push(st);int now,nexts;
    while(!Q.empty())
    {
        now=Q.front();Q.pop();
        for(int i=0;i<G[now].size();i++)
        {
            Edge e=G[now][i];
            if(ranks[e.to]==-1)
            {
                ranks[e.to]=ranks[now]+1;// 深度
                pre[e.to]=now; // 父亲节点
                value[e.to]=e.cost;// 将边权转化为 点权
                Q.push(e.to);
            }
         } 
    }
}
int lca(int st,int ed) //这里用的是步近法来求最近公共祖先,还有更多有效率的算法求最近公共祖先这里就不列举了
{
    int sum=0; // 先使其 都达到同一深度下
    while(ranks[st]>ranks[ed]) sum+=value[st],st=pre[st];
    while(ranks[ed]>ranks[st]) sum+=value[ed],ed=pre[ed];
    while(st!=ed) //  就是开始找相同祖先
    {
        sum+=value[st];
        sum+=value[ed];
        st=pre[st];
        ed=pre[ed];
    } 
    return sum;
}
int main()
{
    read(n);read(q);
    m=n-1;
    getmap();
    bfs(1);
    int st,ed;
    while(q--)
    {
        read(st);read(ed);
         printf("%d\n",lca(st,ed));
     } 
    return 0;
}

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#define inf 0x3f3f3f3f
#define mod 100009
#define LL long long
#define M  100000+100

using namespace std;
struct edge { int to,cost;};
vector<edge>G[M];
int n,m;
int pre[M];
int ranks[M];
int cost[M]; 
void init(){}
void getmap()
{
    int i,j;
    for(i=0;i<n-1;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        G[a].push_back({b,c}) ;
        G[b].push_back({a,c}) ;
     } 
 } 
void dfs(int now,int par,int depth)  // 用dfs跑 
{
    pre[now]=par;
    ranks[now]=depth;
    int i,j;
    for(i=0;i<G[now].size();i++)
    {
        edge e=G[now][i];
        if(e.to!=par) 
        {
            cost[e.to]=e.cost;
            dfs(e.to,now,depth+1);
        }
     } 
}
int LCA(int a,int b)  // 求两个节点的最短距离 
{
    int sum=0;
    while(ranks[a]>ranks[b]) sum+=cost[a],a=pre[a]; // 到达同一深度 
    while(ranks[b]>ranks[a]) sum+=cost[b],b=pre[b];
    while(a!=b)  //  同一 最近公共祖先 
    {
        sum+=cost[a];
        sum+=cost[b];
        a=pre[a];
        b=pre[b];
     }
     return sum; 
}
int main()
{
    scanf("%d%d",&n,&m);
    int i,j;
    getmap();
    dfs(1,-1,0);  // 树根的;pre 为-1  
    while(m--)
    {
        int st,ed;
        scanf("%d%d",&st,&ed);
        printf("%d\n",LCA(st,ed));
        }   
    return 0;
}

2017 5.27 最近学了,LCA转RMQ (不会?点我 ) 赶紧练一下

设dis[x]为树根到x节点的路程,所以只要求出a,b的lca,答案就是dis[a]+dis[b]-2*dis[lca(a,b)]
dis的记录在遍历树的时候可以求得。

代码

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 100000+100
#define MAXM 100000
using namespace std;
struct Edge
{
    int from, to, val,next;
};
Edge edge[MAXN<<1];
int head[MAXN], edgenum;
int vs[MAXN<<1];//第i次DFS访问节点的编号
int depth[MAXN<<1];//第i次DFS访问节点的深度
int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标
int dfs_clock;//时间戳
int N, M, Q;//点数 边数 查询数
int dp[MAXN<<1][20];//dp[i][j]存储depth数组  以下标i开始的,长度为2^j的区间里 最小值所对应的下标
int dist[MAXN];
void init()
{
    edgenum = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v,int c)
{
    Edge E = {u, v,c, head[u]};
    edge[edgenum] = E;
    head[u] = edgenum++;
}
void getMap()
{
    int a, b,c;
    while(M--)
        scanf("%d%d%d", &a, &b,&c),
        addEdge(a, b,c), addEdge(b, a,c);
}
void DFS(int u, int fa, int d)//当前遍历点以及它的父节点  遍历点深度
{
    id[u] = dfs_clock;
    vs[dfs_clock] = u;
    depth[dfs_clock++] = d;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == fa) continue;
        dist[v]=dist[u]+edge[i].val;
        DFS(v, u, d+1);
        vs[dfs_clock] = u;//类似 回溯
        depth[dfs_clock++] = d;
    }
}
void find_depth()
{
    dfs_clock = 1;
    memset(vs, 0, sizeof(vs));
    memset(id, 0, sizeof(id));
    memset(depth, 0, sizeof(depth));
    memset(dist,0,sizeof(dist));
    DFS(1, -1, 0);//遍历
}
void RMQ_init(int NN)//预处理 区间最小值
{
    for(int i = 1; i <= NN; i++)
        dp[i][0] = i;
    for(int j = 1; (1<<j) <= NN; j++)
    {
        for(int i = 1; i + (1<<j) - 1 <= NN; i++)
        {
            int a = dp[i][j-1];
            int b = dp[i + (1<<(j-1))][j-1];
            if(depth[a] <= depth[b])
                dp[i][j] = a;
            else
                dp[i][j] = b;
        }
    }
}
int query(int L, int R)
{
    //查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标
    int k = 0;
    while((1<<(k+1)) <= R-L+1) k++;
    int a = dp[L][k];
    int b = dp[R - (1<<k) + 1][k];
    if(depth[a] <= depth[b])
        return a;
    else
        return b;
}
int LCA(int u, int v)
{
    int x = id[u];//比较大小 小的当作左区间 大的当作右区间
    int y = id[v];
    if(x > y)
        return vs[query(y, x)];
    else
        return vs[query(x, y)];
}
void solve()
{
    int a, b;
    while(Q--)
    {
        scanf("%d%d", &a, &b);
        printf("%d\n",dist[a]+dist[b]-2*dist[LCA(a, b)]);
    }
}
int main()
{
     scanf("%d%d", &N,&Q) ;
        M=N-1;
        init();
        getMap();
        find_depth();//DFS遍历整个树 求出所需要的信息
        RMQ_init(dfs_clock - 1);
        solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值