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掉吧!!!!!
一些感想
所以,最後,我覺得次短路的做法可以當做一個知識點記憶下來(當然前提是要搞明白程式碼是怎麼來的)!共同努力吧!!!