1. 程式人生 > >P1220 第二短路

P1220 第二短路

題目

描述 Description
貝茜把家搬到了一個小農場,但她常常回到FJ的農場去拜訪她的朋友。貝茜很喜歡路邊的風景,不想那麼快地結束她的旅途,於是她每次回農場,都會選擇第二短的路徑,而不象我們所習慣的那樣,選擇最短路。

貝茜所在的鄉村有R(1<=R<=100,000)條雙向道路,每條路都聯結了所有的N(1<=N<=5000)個農場中的某兩個。貝茜居住在農場1,她的朋友們居住在農場N(即貝茜每次旅行的目的地)。

貝茜選擇的第二短的路徑中,可以包含任何一條在最短路中出現的道路,並且,一條路可以重複走多次。當然咯,第二短路的長度必須嚴格大於最短路(可能有多條)的長度,但它的長度必須不大於所有除最短路外的路徑的長度。
輸入格式 Input Format
* 第1行: 兩個整數,N和R,用空格隔開

  • 第2…R+1行: 每行包含三個用空格隔開的整數A、B和D,表示存在一條長度為D(1 <= D <= 5000)的路連線農場A和農場B
    輸出格式 Output Format
    輸出一個整數,即從農場1到農場N的第二短路的長度
    樣例輸入 Sample Input

4 4
1 2 100
2 4 200
2 3 250
3 4 100
樣例輸出 Sample Output

450

輸出說明:
最短路:1 -> 2 -> 4 (長度為100+200=300)
第二短路:1 -> 2 -> 3 -> 4 (長度為100+250+100=450)
時間限制 Time Limitation
1s

第一種解題思路

初次讀完這道題,我就在想:這不就是改改模板的事嗎?(預示著我肯定會WA)
好了,廢話不多說,說一下思路,我首先想到思路就是:用spfa求最短路徑的同時,開一個d2陣列維護一下次短路,最後直接輸出d2【n】即可(即源點到第n點的次短路)。

#include<bits/stdc++.h>
using namespace std;
const int maxm=100010;
const int maxn=5010;
int n, m, tot=0;
long long d1[maxn], d2[maxn];
int head[maxn], next[maxm<<
1], ver[maxm<<1], edge[maxm<<1]; inline int read() { int f=1,num=0; char ch=getchar(); while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); } while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar(); return num*f; } void add(int x,int y,int z) { ver[++tot]=y,edge[tot]=z,next[tot]=head[x],head[x]=tot; ver[++tot]=x,edge[tot]=z,next[tot]=head[y],head[y]=tot; } bool v[maxn]; void spfa() { queue<int>q; memset(d1,0x3f,sizeof(d1)); memset(d2,0x3f,sizeof(d2)); v[1]=1,d1[1]=0; q.push(1); while (!q.empty()) { int x=q.front(); q.pop(); v[x]=0; for (int i=head[x];i;i=next[i]) { int y=ver[i],z=edge[i]; if (d1[y]>d1[x]+z) { d2[y]=d1[y]; d1[y]=d1[x]+z; if (!v[y]) q.push(y),v[y]=1; } else if (d1[y]<d1[x]+z&&d2[y]>d1[x]+z)//這條邊不是最短路的邊 { d2[y]=d1[x]+z; if (!v[y]) q.push(y),v[y]=1; } if (d2[y]>d2[x]+z)//注意不能else { d2[y]=d2[x]+z; if (!v[y]) q.push(y),v[y]=1; } } } } int main() { n=read(),m=read(); for (int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); add(x,y,z); } spfa(); printf("%lld\n",d2[n]); return 0; }

寫完自己想的程式碼後,一直WA,好無奈,上網借鑑一下大佬的寫法,才發現我的陣列開的太大了,導致執行時間太長。。。。。。。。。。。。。。。。。
錯誤的陣列範圍

const int maxnum=999999;
int n, m, tot, d1[maxnum], d2[maxnum], head[maxnum], next[maxnum], ver[maxnum], edge[maxnum];

下面是大佬的程式碼
(喜歡用邊表的大佬們可以著重看一下)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 1LL<<60
#define N 5010
#define M 100010
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m,h[N],num=0;
ll d1[N],d2[N];
bool inq[N];
struct edge
{
    int to,next,v;
}data[M<<1];
void spfa()
{
    queue<int>q;
    for(int i=1;i<=n;++i) d1[i]=d2[i]=inf;
    q.push(1);
    inq[1]=1;
    d1[1]=0;
    while(!q.empty())
    {
        int x=q.front();q.pop();inq[x]=0;
        for(int i=h[x];i;i=data[i].next)
        {
            int y=data[i].to;
            if(d1[x]+data[i].v<d1[y])
            {
                d2[y]=d1[y];d1[y]=d1[x]+data[i].v;
                if(!inq[y]) q.push(y),inq[y]=1;
            }
            else if(d1[x]+data[i].v<d2[y]&&d1[x]+data[i].v>d1[y])
            {//這條邊不是最短路的邊 
                d2[y]=d1[x]+data[i].v;
                if(!inq[y]) q.push(y),inq[y]=1;
            }
            if(d2[x]+data[i].v<d2[y])
            {//注意不能else
                d2[y]=d2[x]+data[i].v;
                if(!inq[y]) q.push(y),inq[y]=1;
            }
        }
    }
}
int main()
{
//  freopen("a.in","r",stdin);
    n=read();m=read();
    while(m--)
    {
        int x=read(),y=read(),v=read();
        data[++num].to=y;data[num].next=h[x];h[x]=num;data[num].v=v;
        data[++num].to=x;data[num].next=h[y];h[y]=num;data[num].v=v;
    }
    spfa();
    printf("%lld\n",d2[n]);
    return 0;

[這是大佬的部落格,(畢竟參考了人家的程式碼)] https://blog.csdn.net/Icefox_zhx/article/details/77881676(https://blog.csdn.net/Icefox_zhx/article/details/77881676)

第二種思路

隨後,發現了一位大佬說了:
次短路的做法好多吧……

可以在spfa的時候順便維護個次短的長度

也可以正反各一遍最短路,然後列舉每一條邊的dis[1][l]+dis[r][n]+v更新次短路(我就是這麼做的)

還可以像k短路一樣倒著跑最短路然後正著A*
。。。。。。。。。。。。。。。。。。。。。。。。。。。
所以在此奉上大佬做的那種程式碼

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int mxn=100005;
queue <int> q;
struct edge {int to,w,next;} f[mxn<<1]; 
bool vis[mxn];
int head[mxn],dis[mxn][2];
int n,m,cnt,ans=1e9,mn;
inline void add(int u,int v,int w)
{
    f[++cnt].to=v,f[cnt].w=w,f[cnt].next=head[u],head[u]=cnt;
    f[++cnt].to=u,f[cnt].w=w,f[cnt].next=head[v],head[v]=cnt;
}
inline void spfa(int s,int t,int k)
{
    int i,j,u,v,w;
    memset(vis,0,sizeof(vis));
    dis[s][k]=0;
    q.push(s);
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        vis[u]=0;
        for(i=head[u];i;i=f[i].next)
        {
            v=f[i].to,w=f[i].w;
            if(dis[v][k]>dis[u][k]+w)
            {
                dis[v][k]=dis[u][k]+w;
                if(!vis[v]) q.push(v),vis[v]=1;
            }
        }
    }
    mn=dis[t][k];
}
int main()
{
    int i,j,u,v,w;
    scanf("%d%d",&n,&m);
    while(m--)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    memset(dis,0x3f,sizeof dis);
    spfa(1,n,0),spfa(n,1,1);  //第1次從起點跑,第二次從終點跑 
    for (u=1;u<=n;u++)
    for(i=head[u];i;i=f[i].next)
    {
        v=f[i].to,w=f[i].w;
        if(dis[u][0]+dis[v][1]+w>mn)
        	ans=min(ans,dis[u][0]+dis[v][1]+w);
    }
    printf("%d\n",ans); 
    return 0;
}

理由同上 https://blog.csdn.net/qq_38246319/article/details/69938878
至於第三種,。。。。。。。。。。。。。。。。還是希望有心人把他A掉吧!!!!!

一些感想

所以,最後,我覺得次短路的做法可以當做一個知識點記憶下來(當然前提是要搞明白程式碼是怎麼來的)!共同努力吧!!!