【洛谷】P1821 [USACO07FEB] Cow Party S(单源最短路)

本文详细解析了在有向图中求解最短路径的问题,包括迪杰斯特拉算法的具体实现与应用,以及如何处理牛参加派对往返的最短路径问题。

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

题目描述

寒假到了, n n n头牛都要去参加一场在编号为 x x x 的牛的农场举行的派对,农场之间有 m m m 条有向路,每条路都有一定的长度。
每头牛参加完派对后都必须回家,无论是去参加派对还是回家,每头牛都会选择最短路径,求这 n n n 头牛的最短路径(一个来回)中最长的一条路径长度。
输入格式
第一行有三个整数,分别表示牛的数量 n n n,道路数 m m m和派对农场编号 x x x
接下来 m m m 行,每行三个整数 u u u, v v v, w w w表示存在一条由 u u u通向 v v v 的长度为 w w w 的道路。
输出格式
输出一行一个整数表示答案。
输入样例
4 8 2
1 2 4
1 3 2
1 4 7
2 1 1
2 3 5
3 1 2
3 4 4
4 2 3
输出样例
10
说明/提示
样例 1 解释
在这里插入图片描述数据规模与约定
对于全部的测试点,保证 1 ≤ x ≤ n ≤ 1 0 3 1≤x≤n≤10^3 1xn103 , 1 ≤ m ≤ 1 0 5 1≤m≤10^5 1m105 1 ≤ u , v ≤ n 1≤u,v≤n 1u,vn, 1 ≤ w ≤ 1 0 2 1≤w≤10^2 1w102,保证从任何一个结点出发都能到达 x x x号结点,且从 x x x 出发可以到达其他所有节点。

思路:

  • 题意概括:牛去参加派对,参加完还要回家。这些牛去派对和回家的过程都会选择走最短的路,求所有最短路中最长的一条。
  • 分析一:由于这是有向图,出去的路和回来的路彼此独立,相互之间没有影响,所以对于每头牛可以分别考虑其出去的最短路和回来的最短路,出去的最短路加回来的最短路就是一个来回的最短路,最后在这些一个来回的最短路中选一条最长的即可。
  • 分析二:对于回来的最短路,我们只要以派对农场对应结点为源点求一次单源最短路就可以得到每头牛回家时的最短路。但出去时的最短路怎么求呢?可能很暴力的想法就是把每投牛对应的结点当作源点求一个单源最短路什么的。但是大可不必这样做!一个巧妙求出去最短路的做法就是把所有边反向,所有边反向之后求“出去的最短路”就转化成了以派对农场为源点的单源最短路问题!
  • 综上,此题的解法就是:将所有边反向后以派对农场为源点求一次单源最短路(得到每头牛回家时的最短路)。然后保持原有边的方向,同样以派对农场为源点求一次单源最短路就得到每头牛回家时的最短路。最后在所有来回最短路中选一条最长的即可。

dijkstra算法简介:

  • dijkstra算法可以求得源点s到这个图中其它所有点的最短路。(注意:这个图一定不能含有负权边)。

  • dijkstra算法具体步骤:
    (1)把所有的点分为已知最短路径的点和未知最短路径的点。用dis[ i i i]表示点 i i i的最短路径。对于源点 s s s,我们 将最短路径初始化为0(源点到源点的最短路为0),即dis[ s s s]=0。对于非源点i,我们将dis[ i i i]初始化为 ∞ \infty ,表示 i i i到源点的距离未知或是 i i i与源点之间没有路径。规定 w i j w_{ij} wij表示图上点 i i i到点 j j j的边的距离。再引入松弛的概念:如下图,3->5->8的路径长度为74,3->8的路径长度为88,通过中间结点5把3到8的最短路从88改为了74。简单的说,松弛就是通过中间结点把某两点间原本的距离变小了。
    在这里插入图片描述
    (2)首先将源点 s s s可以直接达的点 i i i的最短路更新为 w s i w_{si} wsi,即dis[ i i i]= w s i w_{si} wsi。此时,离源点 s s s最近的点 j j j的最短路已经确定(因为这个点离源点最近,所以已经不能再通过其它点松弛),于是将 j j j标记为最短路已知的点。
    (3)接下来,我们用上面已经确定了最短路径的点 j j j去松弛 j j j可以直接到达的点的最短距离,同样在这些已经被松弛的点中选一个最短路径未知的且目前距离源点最近的点加入队列并标记为最短路径已知。
    (4)重复(3)中的“在这些已经松弛过但最短路未知的点中选一个距离源点最近的点,将其标记为最短路已知,并用这个点去松弛其它点到源点的距离”直到使用的点都标记为最短路径已知。

    盗一张百度百科的动图给你们感受一下:

在这里插入图片描述

dijkstra算法核心代码:

 / /本模板采用链式前向星存图,并通过优先队列优化
       const int inf=0x3f3f3f3f;
       struct edge{
       int u;
       int v;
       int w;
       int next;
       }e[maxm];
       int head[maxn],dis[maxn],cnt=0;
       void add(int u,int v,int w){   / /链式前向星加边
       cnt1++;
       e[cnt].u=u;
       e[cnt].v=v;
       e[cnt].w=w;
       e[cnt].next=head[u];
       head[u]=cnt;
       }
       struct  node{    / /用结构体记录结点u及其到源点的距离dis
       int u,dis;
       / /定义比较函数使得优先队列顶端可以取到距离源点最短的结点
       bool operator<(const node& b) const{  
       return dis>b.dis;
       }
	   };
	   int n,m,x;
       void  disjkstra(int s){
       for(int i=1;i<=n;i++){
       dis[i]=inf;
       }   
       priority_queue<node>que;
       dis[s]=0;
       que.push((node){s,dis[s]});
       while(!que.empty()){
       node now=que.top();
       que.pop();
       int  u=now.u;
       if(now.dis!=dis[u]) continue; 
       / /now.dis等于dis[u]则此结点的最短路已经确定,可以用它去更新其它结点的最短路。now.dis不等于dis[u]时就仅将其出队就行了。
       for(int i=head[u];i>0;i=e[i].next){     / /访问u的所有出边
        int v=e[i].v;
        int w=e[i].w;
        if(dis[v]>dis[u]+w){      / /用u去松弛它可以到达的边
        dis[v]=dis[u]+w;
        que.push((node){v,dis[v]});
        }
       }
       }
       }

本题dijkstra解法AC代码

#include<bits/stdc++.h>
       using namespace std;
       const int maxn=1e3+50;
       const int maxm=1e5+50;
       const int inf=0x3f3f3f3f;
       struct edge{
       int u;
       int v;
       int w;
       int next;
       }e1[maxm],e2[maxm];
       int head1[maxn],dis1[maxn],cnt1=0;
       int head2[maxn],dis2[maxn],cnt2=0;
       void add1(int u,int v,int w){
       cnt1++;
       e1[cnt1].u=u;
       e1[cnt1].v=v;
       e1[cnt1].w=w;
       e1[cnt1].next=head1[u];
       head1[u]=cnt1;
       }
       void add2(int u,int v,int w){
       cnt2++;
       e2[cnt2].u=u;
       e2[cnt2].v=v;
       e2[cnt2].w=w;
       e2[cnt2].next=head2[u];
       head2[u]=cnt2;
       }
       struct  node{
       int u,dis;
       bool operator<(const node& b) const{
       return dis>b.dis;
       }
	   };
       int n,m,x;
       void  disjkstra1(int s){
       for(int i=1;i<=n;i++){
       dis1[i]=inf;
       }
       priority_queue<node>que;
       dis1[s]=0;
       que.push((node){s,dis1[s]});
       while(!que.empty()){
       node now=que.top();
       que.pop();
       int  u=now.u;
       if(now.dis!=dis1[u]) continue;
       for(int i=head1[u];i>0;i=e1[i].next){
        int v=e1[i].v;
        int w=e1[i].w;
        if(dis1[v]>dis1[u]+w){
        dis1[v]=dis1[u]+w;
        que.push((node){v,dis1[v]});
        }
       }
       }
       }
       void  disjkstra2(int s){
       for(int i=1;i<=n;i++){
       dis2[i]=inf;
       }
       priority_queue<node>que;
       dis2[s]=0;
       que.push((node){s,dis2[s]});
       while(!que.empty()){
       node now=que.top();
       que.pop();
       int  u=now.u;
       if(now.dis!=dis2[u]) continue;
       for(int i=head2[u];i>0;i=e2[i].next){
        int v=e2[i].v;
        int w=e2[i].w;
        if(dis2[v]>dis2[u]+w){
        dis2[v]=dis2[u]+w;
        que.push((node){v,dis2[v]});
        }
       }
       }
       }
       int main(){
        //freopen("inn.txt","r",stdin);
        //freopen("outt.txt","w",stdout);
       scanf("%d%d%d",&n,&m,&x);
       for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add1(u,v,w);
        add2(v,u,w);
        }
       disjkstra1(x);
       disjkstra2(x);
       int ans=-1;
       for(int i=1;i<=n;i++)
       ans= max(ans,dis1[i]+dis2[i]);
       printf("%d\n",ans);
       //fclose(stdin);
       //fclose(stdout);
       return 0;
       }


spfa解法:

单源最短路也可以用spfa来做,但是这个题好像卡这种做法。所以就发个代码,不多余展开。

spfa70分代码

#include<bits/stdc++.h>
       using namespace std;
       const int maxn=1e3+50;
       const int maxm=1e5+50;
       const int INF=0x3f3f3f3f;
       struct edge{
       int u;
       int v;
       int w;
       int next;
       }e1[maxm],e2[maxn];
       int head1[maxn],dis1[maxn],inq1[maxn],cnt1=0;
       int head2[maxn],dis2[maxn],inq2[maxn],cnt2=0;
       void add1(int u,int v,int w){
       cnt1++;
       e1[cnt1].u=u;
       e1[cnt1].v=v;
       e1[cnt1].w=w;
       e1[cnt1].next=head1[u];
       head1[u]=cnt1;
       }
       void add2(int u,int v,int w){
       cnt2++;
       e2[cnt2].u=u;
       e2[cnt2].v=v;
       e2[cnt2].w=w;
       e2[cnt2].next=head2[u];
       head2[u]=cnt2;
       }
       int n,m,x;
       void spfa1(int s){
       memset(dis1, INF, sizeof(dis1));
       queue<int>q;
       q.push(s);
       dis1[s]=0;
       inq1[s]=1;
       while(!q.empty()){
       int u=q.front();
       q.pop();
       inq1[u]=0;
       for(int i=head1[u];i>0;i=e1[i].next){
        int v=e1[i].v;
        int w=e1[i].w;
       if(dis1[v]>dis1[u]+w){
       dis1[v]=dis1[u]+w;
       if(!inq1[v]){
       q.push(v);
       inq1[v]=1;
       }
       }
       }
       }
       }
       void spfa2(int s){
        memset(dis2, INF, sizeof(dis2));
       queue<int>q;
       q.push(s);
       dis2[s]=0;
       inq2[s]=1;
       while(!q.empty()){
       int u=q.front();
       q.pop();
       inq2[u]=0;
       for(int i=head2[u];i>0;i=e2[i].next){
        int v=e2[i].v;
        int w=e2[i].w;
       if(dis2[v]>dis2[u]+w){
       dis2[v]=dis2[u]+w;
       if(!inq2[v]){
       q.push(v);
       inq2[v]=1;
       }
       }
       }
       }
       }
       int main(){
      // freopen("inn.txt","r",stdin);
       //freopen("outt.txt","w",stdout);
       scanf("%d%d%d",&n,&m,&x);
       for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add1(u,v,w);
        add2(v,u,w);
        }
       spfa1(x);
       spfa2(x);
       int ans=-1;
       for(int i=1;i<=n;i++)
       ans= max(ans,dis1[i]+dis2[i]);
       printf("%d\n",ans);
        //fclose(stdin);
        //fclose(stdout);
       return 0;
       }

spfa双端队列优化70分代码

#include<bits/stdc++.h>
       using namespace std;
       const int maxn=1e3+50;
       const int maxm=1e5+50;
       const int INF=0x3f3f3f3f;
       struct edge{
       int u;
       int v;
       int w;
       int next;
       }e1[maxm],e2[maxn];
       int head1[maxn],dis1[maxn],inq1[maxn],cnt1=0;
       int head2[maxn],dis2[maxn],inq2[maxn],cnt2=0;
       void add1(int u,int v,int w){
       cnt1++;
       e1[cnt1].u=u;
       e1[cnt1].v=v;
       e1[cnt1].w=w;
       e1[cnt1].next=head1[u];
       head1[u]=cnt1;
       }
       void add2(int u,int v,int w){
       cnt2++;
       e2[cnt2].u=u;
       e2[cnt2].v=v;
       e2[cnt2].w=w;
       e2[cnt2].next=head2[u];
       head2[u]=cnt2;
       }
       int n,m,x;
       void spfa1(int s){
       memset(dis1, INF, sizeof(dis1));
       deque<int>q;
       q.push_back(s);
       dis1[s]=0;
       inq1[s]=1;
       while(!q.empty()){
       int u=q.front();
       q.pop_front();
       inq1[u]=0;
       for(int i=head1[u];i>0;i=e1[i].next){
        int v=e1[i].v;
        int w=e1[i].w;
       if(dis1[v]>dis1[u]+w){
       dis1[v]=dis1[u]+w;
       if(!inq1[v]){
       inq1[v]=1;
       if(!q.empty()&&dis1[v]<=dis1[q.front()])
       q.push_front(v);
       else q.push_back(v);
       }
       }
       }
       }
       }
       void spfa2(int s){
        memset(dis2, INF, sizeof(dis2));
       deque<int>q;
       q.push_back(s);
       dis2[s]=0;
       inq2[s]=1;
       while(!q.empty()){
       int u=q.front();
       q.pop_front();
       inq2[u]=0;
       for(int i=head2[u];i>0;i=e2[i].next){
        int v=e2[i].v;
        int w=e2[i].w;
       if(dis2[v]>dis2[u]+w){
       dis2[v]=dis2[u]+w;
       if(!inq2[v]){
       inq2[v]=1;
       if(!q.empty()&&dis1[v]<=dis1[q.front()])
       q.push_front(v);
       else q.push_back(v);
       }
       }
       }
       }
       }
       int main(){
      // freopen("inn.txt","r",stdin);
       //freopen("outt.txt","w",stdout);
       scanf("%d%d%d",&n,&m,&x);
       for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add1(u,v,w);
        add2(v,u,w);
        }
       spfa1(x);
       spfa2(x);
       int ans=-1;
       for(int i=1;i<=n;i++)
       ans= max(ans,dis1[i]+dis2[i]);
       printf("%d\n",ans);
        //fclose(stdin);
        //fclose(stdout);
       return 0;
       }

上一篇博客:【leetcode】785. 判断二分图 (二分图&bfs)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值