P1629 邮递员送信

题目描述

有一个邮递员要送东西,邮局在节点 1。他总共要送 n−1 样东西,其目的地分别是节点 2 到节点 n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n−1 样东西并且最终回到邮局最少需要的时间。

输入格式

第一行包括两个整数,n 和 m,表示城市的节点数量和道路数量。

第二行到第 (m+1) 行,每行三个整数,u,v,w 表示从 u 到 v 有一条通过时间为 w 的道路。

输出格式

输出仅一行,包含一个整数,为最少需要的时间。

输入输出样例

输入
5 10
2 3 5
1 5 5
3 5 6
1 2 8
1 3 8
5 3 4
4 1 8
4 5 3
3 5 6
5 4 2
输出
83

这一看就是用单源最短路径求解答案,首先去第2到n个节点就不用说了,就是1到每个点的最短路径,这个在单元最短路里面讲过,接着就要看走回来这个问题

走回来怎么弄,以2到n每个点为根据点求出最短路径?这样肯定不行,TLE等着你

那怎么弄,其实仔细想一想,就不难想到,我们可以把有向图反过来存,还是以1为基础求单元最短路,dis里面存的就是2到n距离1的最短路了

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5;
int n,m;
struct node{
	int to,dis;
	friend bool operator <(node a,node b){
		return a.dis>b.dis;
	}
};
vector<node> a[N];
vector<node> b[N];
int dis[N];
int dis2[N];
int vis[N];
int vis2[N];
int s;
priority_queue<node>q1;
priority_queue<node>q2;
void dij_start(){
	for(int i=1;i<=n;i++)dis[i]=INT_MAX;
	dis[s]=0;
	q1.push(node{s,0});
	while(!q1.empty()){
		node t=q1.top();
		q1.pop();
		int x=t.to;
		if(vis[x]==1)continue;
		vis[x]=1;
		for(int i=0;i<a[x].size();i++){
			int y=a[x][i].to;
			int z=a[x][i].dis;
			dis[y]=min(dis[y],dis[x]+z);
			if(vis[y]==0)q1.push(node{y,dis[y]});
		}
	}
}
void dij_over(){
	for(int i=1;i<=n;i++)dis2[i]=INT_MAX;
	dis2[s]=0;
	q2.push(node{s,0});
	while(!q2.empty()){
		node t=q2.top();
		q2.pop();
		int x=t.to;
		if(vis2[x]==1)continue;
		vis2[x]=1;
		for(int i=0;i<b[x].size();i++){
			int y=b[x][i].to;
			int z=b[x][i].dis;
			dis2[y]=min(dis2[y],dis2[x]+z);
			if(vis2[y]==0)q2.push(node{y,dis2[y]});
		}
	}
}
signed main(){
	scanf("%lld%lld",&n,&m);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&u,&v,&w);
		a[u].push_back(node{v,w});
		b[v].push_back(node{u,w});
	}
	s=1;
	dij_start();
	dij_over();
	int sum=0;
	for(int i=2;i<=n;i++)sum+=dis[i]+dis2[i];
	printf("%lld",sum);
}

<think>好的,我现在需要仔细分析用户提供的代码,并解释它的工作原理。这个题目是P1629邮递员送信问题,看起来是关于图论的,可能涉及到最短路径的问题。首先,我需要理解代码的结构和各个部分的作用。 首先,代码包含了头文件iostream、vector、queue和climits,这些都是C++标准库中的常用头文件,用于输入输出、容器、优先队列和整数限制。接下来使用了命名空间std,避免重复写std::前缀。 然后定义了一个常量INF为INT_MAX,这在Dijkstra算法中通常用来表示无穷大,即不可达的初始距离。 接下来是Dijkstra函数的实现。函数的参数是起点start和图graph。图的表示方式是邻接表,每个节点对应一个vector,存储的是pair,其中第一个元素是目标节点v,第二个是权重w。函数返回一个dist数组,记录从起点到各个节点的最短距离。 在Dijkstra函数内部,首先初始化dist数组为INF,然后使用优先队列pq作为最小堆,存储当前的距离和节点。起点到自己的距离设为0,并加入队列。然后进入循环,每次取出距离最小的节点u,如果当前的距离d大于记录的dist[u],说明已经有更优的路径,跳过。否则,遍历u的所有邻接节点v,尝试松弛操作:如果通过u到v的路径更短,就更新dist[v],并将新的距离和节点v加入队列。这个过程是标准的Dijkstra算法的优先队列实现。 主函数main中,首先读取n和m,n是节点数,m是边数。然后创建了两个邻接表:original和reversed。original是原图的结构,而reversed是反向图,即所有的边都反转方向。例如,原图中的边u->v在反向图中变为v->u。这一步可能用于处理返回路径的问题,因为邮递员需要从起点出发到各个节点,然后再返回,所以返回的时候可能需要反向图来计算最短路径。 接下来,使用Dijkstra算法两次:一次在原图上计算从节点1出发到各点的最短距离(dist_go),另一次在反向图上计算节点1到各点的最短距离(dist_back)。这里需要注意的是,反向图的Dijkstra实际上相当于在原图中计算各点到节点1的最短路径。例如,在反向图中,边是v->u,所以在原图中其实是u->v的边,因此Dijkstra处理反向图时,得到的是原图中各节点到节点1的最短路径。 然后,遍历所有节点,将去程和返程的最短路径相加,得到总和sum,并输出这个总和。 现在需要确认的是,为什么反向图的Dijkstra能够正确计算返程的最短路径。假设原图中有边u->v,权值w。在反向图中,这个边被存储为v->u,权值w。当在反向图上运行Dijkstra(1),相当于计算原图中各个节点到1的最短路径。例如,若在原图中,从节点A到1的最短路径需要经过A→B→1,那么在反向图中,边会被视为B→A和1→B。当以1为起点在反向图中运行Dijkstra,实际上找到的是1到各个节点的路径,也就是原图中各个节点到1的最短路径的反向。例如,反向图中的路径1→B→A对应原图中的路径A→B→1,权值相同。因此,反向图的Dijkstra结果dist_back[i]实际上是原图中i到1的最短距离。 这样,总路程就是去程(1到i的最短距离)加上返程(i到1的最短距离)的总和,对所有i求和。 现在需要验证这个思路是否正确。例如,当原图是单向边时,反向图是否正确处理了返程的问题。例如,如果有边1->2,权3,那么反向图中有边2->1,权3。当在反向图中计算从1出发的最短路径到2,实际上是原图中从2到1的最短路径是否存在。如果原图中只有单向边1->2,那么原图中2无法到达1,此时返程的距离会是INF吗?或者如果反向图中存在其他边的话,比如在反向图中可能存在其他路径,比如2→3→1,这时候在反向图中Dijkstra(1)会找到1到其他节点的路径,对应原图中的节点到1的路径。 可能存在的疑问是,当原图中有多个边时,反向图的构造是否能够正确捕捉所有可能的返程路径。例如,原图中如果存在u→v的边,而返程可能需要经过其他路径,比如v→w→u,那么在反向图中,需要存在v→u的边和w→v的边吗?或者反向图的构造是否将所有原图的边反转,从而在反向图中运行Dijkstra(1)相当于计算原图中各节点到1的最短路径? 是的,反向图的构造将所有原图的边反转,这样在反向图中,Dijkstra(1)找到的最短路径,对应的是原图中各节点到1的最短路径。例如,原图中的边u→v,在反向图中是v→u。假设在原图中,从节点i到1的最短路径是i→a→b→1,那么反向图中的边会是a←i,b←a,1←b。当在反向图中以1为起点运行Dijkstra,会找到路径1→b→a→i,对应的权值之和等于原路径的权值之和。因此,dist_back[i]即为原图中i到1的最短距离。 所以,这种方法是正确的,通过两次Dijkstra算法,分别计算去程和返程的总和,从而得到邮递员的总路程。 接下来,主函数中的sum是各个节点的去程和返程之和的总和。例如,对于每个节点i,sum += dist_go[i] + dist_back[i]。最终输出sum。 需要注意的是,题目中的邮递员是否需要访问每个节点一次,并且最终回到起点?例如,题目是否要求从1出发,访问所有节点后再返回?或者是否需要每次送信到各个节点后立即返回?可能题目中的模型是,邮递员需要从1出发,到达各个节点一次(送信),然后从各个节点返回1。因此,总路程是每个节点到1的最短路径(去)加上1到每个节点的最短路径(返)的总和。或者,可能题目中的模型是,邮递员去的时候从1出发到各个节点送信,然后返回时从各个节点回到1。但根据代码的逻辑,sum是将每个节点的去程和返程相加,这可能意味着对于每个节点i,邮递员需要从1到i送信,然后从i返回1。所以总路程是每个节点的往返路程之和。例如,如果共有n个节点,每个节点都要去一次并回来一次,所以总路程是各个节点的(1到i + i到1)的总和。 例如,n=4,那么总路程是(1→1的往返)+(1→2的往返)+(1→3的往返)+(1→4的往返)?或者是否题目中的邮递员需要一次出发,访问所有节点,再返回?此时可能需要走一个环路,但这样总路程可能不是简单的相加。但根据代码的逻辑,显然是每个节点单独计算往返,这可能题目中的要求是邮递员必须为每个节点单独送信,即每次出发送一个节点,所以总路程是每个节点的往返总和。或者,题目中的邮递员需要一次出发,访问所有节点,然后返回,此时总路程是1到所有节点的最短路径之和(去的时候的最优路径访问所有节点),再加上所有节点到1的最短路径之和(返程)。这种情况下,代码可能并不正确,因为访问多个节点可能需要经过其他节点,但代码中的处理方式是将每个节点的去程和返程单独相加,这可能适用于每个任务都是单独往返的情况。 根据题目名称“邮递员送信”,可能每个信件需要单独送到各个节点,即邮递员必须从1出发,送到节点i,然后返回1,再出发送到节点i+1,再返回。这种情况下,总时间就是各个节点往返时间的总和。此时,代码的处理方式是正确的,因为对于每个节点i,去的时间是1到i的最短路径,返回的时间是i到1的最短路径。因此,总时间就是sum(去i + 返i)对所有i。 所以,代码的正确性基于这样的问题模型:邮递员需要为每个节点单独送信,每次都是从1出发到该节点再返回,总路程是各次往返的总和。因此,两次Dijkstra的正确性保证了去程和返程的最短路径之和。 总结来说,这段代码的主要思路是: 1. 构建原图和反向图。 2. 在原图上运行Dijkstra,得到1到各节点的最短路径(去程)。 3. 在反向图上运行Dijkstra,得到各节点到1的最短路径(返程)。 4. 将所有去程和返程的距离相加,得到总和。 这种方法的优势在于,不需要为每个节点单独运行Dijkstra来计算返程路径,而是通过反向图一次得到所有节点到1的最短路径,从而将时间复杂度降低到O(M + N log N)两次Dijkstra的时间,而不是O(N(M + N log N)),如果对每个节点都运行一次Dijkstra的话。 可能的疑问是,为什么反向图能够正确计算返程路径?这已经在前面的分析中解答,即反向图的Dijkstra结果对应原图中的返程路径。 此外,代码中需要注意的细节: - 节点编号从1开始,因此在初始化graph的时候是n+1的大小。 - 使用优先队列优化Dijkstra算法,时间复杂度为O(M log N)。 - 处理输入时,将原边和反向边分别加入original和reversed图中。 - sum的类型是long long,避免整数溢出,因为当边数较多且权重较大时,总和可能超过int的范围。 可能的测试样例,比如当n=1时,总和应该是0,去程和返程都是0。或者当存在单向边时,是否能正确处理不可达的情况?比如如果某个节点无法到达,那么dist_go或dist_back可能为INF,导致总和溢出,但题目可能保证所有节点都是可达的,或者在代码中未处理这种情况,这可能导致结果错误。但题目可能有输入条件保证所有节点可达,所以代码中没有处理INF的情况。 综上,这段代码通过两次Dijkstra高效地解决了邮递员送信往返的总距离问题,利用了反向图来优化计算各节点到起点的最短路径。</think>这个代码解决的是邮递员送信问题,计算从起点出发到所有节点送信并返回的总最短路径。以下是分步解释: ### 代码结构 1. **Dijkstra算法实现** - 函数`dijkstra(int start, ...)`用优先队列优化,计算从起点到所有节点的最短路径。 - 输入:起点`start`和邻接表`graph`。 - 输出:最短距离数组`dist`。 2. **反向建图** - 构建原图`original`和反向图`reversed`: - 原图存储正向边(如`u→v`)。 - 反向图存储逆向边(如`v→u`),用于计算返程路径。 3. **两次Dijkstra计算** - `dist_go`:原图上计算**去程**(从1号点到所有点的最短路径)。 - `dist_back`:反向图上计算**返程**(从所有点返回1号点的最短路径)。 4. **累加总路径** - 对每个节点`i`,总路程为去程`dist_go[i]` + 返程`dist_back[i]`。 ### 关键点解释 1. **反向图的作用** - 在反向图中运行Dijkstra,等价于计算原图中所有节点到起点1的最短路径。例如: 反向图中的边`v→u`对应原图的`u→v`,因此从1出发到`v`的最短路径,对应原图中`v`到1的最短路径。 2. **复杂度优化** - 仅需2次Dijkstra操作,时间复杂度为$O(M \log N)$,远优于对每个节点单独计算返程的$O(N^2 \log N)$。 ### 示例说明 假设输入为: ``` 3 3 1 2 5 2 3 10 3 1 2 ``` - **去程计算**:`dist_go[2] = 5`(1→2),`dist_go[3] = 15`(1→2→3)。 - **返程计算**: - 反向图将边反向:`2→1`, `3→2`, `1→3`。 - `dist_back[2]`通过反向图的`2→1`直接得到5,`dist_back[3]`通过反向图的`3→1`直接得到2。 - **总和**:5+5(节点2) +15+2(节点3) +0+0(节点1)= 27。 ### 总结 代码通过正反向图的Dijkstra计算,高效求解邮递员往返所有节点的总最短路径,时间复杂度为$O(M \log N)$,适用于大规模数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值