洛谷P1629 邮递员送信(dijkstra算法+反向建图)

本文深入探讨了在大规模数据集上使用双向Dijkstra算法解决单源最短路径问题的高效方法,通过构建反向图避免了Floyd算法的时间复杂度过高的问题。详细介绍了如何在正向和反向图中应用Dijkstra算法,以及如何通过优先队列优化搜索过程。

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

在这里插入图片描述原题链接
思路:
答案包含两部分
1.从1到其它所有点i的最短距离的和。
2.从其它所有点i到1的最短距离的和。
注意是单向边。
第一反应是用floyed算法但是1e3的数据量明显超时,最后我试了一下只有四十分。
看了别人的题解知道了还有建反向图这种操作,我在这详细说明一下。建反向图其实就是:
比如d[i][j]代表从i到j的距离,反向的话就是把d[i][j]和d[j][i]互换过来。
原理是这样的:
从u到v距离是w,那么从v到u距离也是w,单纯把方向反过来。说的话感觉总是说不到那个点子上,实在不行就画个图瞅瞅。
然后跑两遍dijkstra就行了。
代码:

#include<iostream>
#include<cmath>
#include<vector>
#include<string>
#include<fstream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e3+10,M=1e5+10;
struct edge
{
    int to,next,w;
};
struct node
{
    int to,w;
    node(int u=0,int ww=0):to(u),w(ww){}//构造函数
    bool operator<(const node&a)const
    {
        return w>a.w;
    }
};
edge e1[M],e2[M];
int head1[N],head2[N],cnt=0,n,m;
int dis_1[N],dis_2[N];
bool done_1[N],done_2[N];
void addedge(int u,int v,int w)
{
    e1[++cnt].to=v;
    e1[cnt].w=w;
    e1[cnt].next=head1[u];
    head1[u]=cnt;
    e2[cnt].to=u;
    e2[cnt].next=head2[v];
    e2[cnt].w=w;
    head2[v]=cnt;
}
void dijkstra_1()//正向图
{
    for(int i=1;i<=n;i++) dis_1[i]=inf,done_1[i]=false;
    dis_1[1]=0;
    priority_queue<node>q;
    q.push(node(1,0));
    while(!q.empty())
    {
        node s=q.top();
        q.pop();
        if(done_1[s.to]) continue;
        done_1[s.to]=1;
        for(int i=head1[s.to];i;i=e1[i].next)
        {
            if(dis_1[s.to]+e1[i].w<dis_1[e1[i].to])
            {
                dis_1[e1[i].to]=dis_1[s.to]+e1[i].w;
                q.push(node(e1[i].to,dis_1[e1[i].to]));
            }
        }
    }
}
void dijkstra_2()//反向图
{
    for(int i=1;i<=n;i++) dis_2[i]=inf,done_2[i]=false;
    dis_2[1]=0;
    priority_queue<node>q;
    q.push(node(1,0));
    while(!q.empty())
    {
        node s=q.top();
        q.pop();
        if(done_2[s.to]) continue;
        done_2[s.to]=1;
        for(int i=head2[s.to];i;i=e2[i].next)
        {
            if(dis_2[s.to]+e2[i].w<dis_2[e2[i].to])
            {
                dis_2[e2[i].to]=dis_2[s.to]+e2[i].w;
                q.push(node(e2[i].to,dis_2[e2[i].to]));
            }
        }
    }
}
int main()
{
    int u,v,w;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    int ans=0;
    dijkstra_1();
    dijkstra_2();
    for(int i=2;i<=n;i++) ans+=dis_1[i]+dis_2[i];
    printf("%d\n",ans);
    return 0;
}
<think>好的,我现在需要回答用户关于Dijkstra算法反向的实现方法或原理的问题。首先,我得回顾一下Dijkstra算法的基本概念,确保自己理解正确。Dijkstra算法是一种用于在带权中找到单源最短路径的贪心算法,适用于边权非负的情况,这在引用[3]和[4]中都有提到。 用户提到了“反向”,这让我联想到可能是在双向Dijkstra算法中的应用。根据引用[2],双向Dijkstra算法通过从起点和终点同时进行搜索,直到两个方向的搜索相遇,从而提高效率。这时候可能需要反向,即构一个反向结构,以便从终点开始反向搜索。 接下来,我需要详细解释反向的原理。反向通常是将原中的所有边的方向反转得到的新。例如,原中有一条边从A到B,权重为w,那么在反向中,这条边会变成从B到A,权重保持w不变。这样做的好处是,当从终点开始反向搜索时,可以沿用同样的Dijkstra算法逻辑,但遍历的是反向边,从而找到从原终点到起点的路径。这在双向搜索中尤其有用,因为两个方向的搜索可以更快地相遇,减少搜索范围,提升性能。 然后,我需要考虑实现步骤。首先,如何构反向?对于邻接表或邻接矩阵表示的有向,需要遍历所有边并反转它们的方向。例如,在Python中,如果使用字典表示邻接表,可以创一个新的字典,遍历原中的每个节点和其邻居,将邻居作为新字典的键,添加反向边。这一步需要注意保留原边的权重。 接着是双向Dijkstra算法的实现。用户提到的引用[2]中提到了双向Dijkstra的注意事项,比如起点和终点必须可达,不能有负权边。在实现时,需要维护两个优先队列,分别处理正向和反向的搜索。每次迭代中,选择当前距离较小的方向进行扩展。当两个方向的已访问节点有交集时,检查是否存在更短的路径,并合并两个路径得到最终结果。 此外,要提到反向的应用场景,比如在交通网络或社交网络中,当需要频繁查询起点到终点和终点到起点的路径时,反向可以预先构,加快查询速度。同时,在双向搜索中,反向可以显著减少搜索的节点数量,尤其是在大规模中效果明显。 还需要注意,反向后的Dijkstra算法与原算法在步骤上是一致的,只是的边方向不同。因此,算法的时间复杂度和空间复杂度与原算法相同,都是O((E + V) log V)(使用优先队列优化的情况下)。不过,双向搜索可以实际中减少运行时间,因为两个方向的搜索范围更小。 最后,我应该验证自己的思路是否正确。比如,反向的构是否确实反转了所有边,双向搜索的终止条件是否正确,以及如何合并路径。例如,当正向搜索访问了节点A,反向搜索也访问了A时,总路径长度是正向到A的距离加上反向到A的距离,这时候需要检查所有这样的交点,找到最小的总距离作为候选的最短路径。 可能还需要考虑代码示例,但用户没有明确要求,不过根据示例中的Python代码,或许可以给出一个简单的反向的例子,比如使用networkx库中的函数或手动构反向邻接表。例如,在networkx中,可以使用reverse()方法直接生成反向,这在引用[1]和[3]中提到的networkx应用可能相关。 总结来说,反向Dijkstra算法中的应用主要是为了支持双向搜索,通过构反向,从终点开始反向执行Dijkstra算法,从而与正向搜索结合,更快地找到最短路径。这需要正确反转所有边的方向,并协调两个方向的搜索过程,确保正确终止和路径合并。</think>### Dijkstra算法中的反向原理与实现方法 #### 1. **反向的原理** 反向(Reverse Graph)的核心思想是**将有向中的所有边方向反转**,同时保持边权不变。例如原存在边 $A \rightarrow B$(权重为 $w$),反向中则变为 $B \rightarrow A$(权重仍为 $w$)。这一操作在以下场景中特别有用: - **双向Dijkstra算法**:从起点和终点同时进行正向和反向搜索,减少搜索范围[^2]。 - **终点到起点的路径查询**:通过反向可直接用Dijkstra算法计算终点到其他节点的最短路径。 #### 2. **反向的构步骤** 以邻接表表示的有向为例: 1. **遍历原的所有边**,记录每条边的起点、终点和权重。 2. **反转边的方向**,将原边的终点作为新起点,起点作为新终点,权重保持不变。 3. **构反向邻接表**:将反转后的边按新起点分类存储。 **Python示例代码(手动构反向):** ```python def build_reverse_graph(graph): reverse_graph = {} for u in graph: for v, w in graph[u]: if v not in reverse_graph: reverse_graph[v] = [] reverse_graph[v].append((u, w)) return reverse_graph # 原示例(邻接表) original_graph = { 'A': [('B', 2), ('C', 5)], 'B': [('C', 1)], 'C': [] } # 构反向 reverse_graph = build_reverse_graph(original_graph) # 输出:{'B': [('A', 2)], 'C': [('A', 5), ('B', 1)]} ``` #### 3. **双向Dijkstra算法中的反向应用** 在双向Dijkstra算法中: - **正向搜索**:从起点开始,使用原进行Dijkstra算法。 - **反向搜索**:从终点开始,使用反向进行Dijkstra算法。 - **终止条件**:当正向和反向搜索的已访问节点集合存在交集时,合并路径。 **关键实现步骤:** 1. 维护两个优先队列,分别处理正向和反向搜索。 2. 每次选择当前距离较小的方向扩展。 3. 当发现共同访问的节点时,计算正向距离 + 反向距离的最小值作为候选最短路径。 #### 4. **性能优势** - **时间复杂度**:双向搜索的理论时间复杂度仍为 $O((E + V) \log V)$,但实际运行时间可能减少约50%。 - **空间复杂度**:需额外存储反向,空间复杂度为 $O(E + V)$。 #### 5. **注意事项** - **负权边**:Dijkstra算法不适用于含负权边的反向同样受此限制[^3]。 - **可达性**:需确保起点和终点在反向中也是可达的。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值