传送门:Slow Leak
前言:此题为牛客ICPC模拟赛上的 I 题,据说出自欧洲某地区的选拔赛。比赛的时候因为时间不够了就没写出来。就算有时间也不会 看了题解之后觉得很妙,看不懂题解,认为题解在教我仙术所以就单独把此题拿出来聊聊。
题目如下:
睡前聊一聊:
初看此题,我一度认为这就是一道求单源最短路径的题,直接上SPFA+记忆步数+特判就可以了,因为那时距离比赛结束只有25min了,所以就只能死马当活马医,A了血赚,WA了不亏。于是队友立刻用我口胡的算法写了一发SPFA,蒟蒻永远充满幻想,于是总是受到社会的毒打 不出意外, WA飞了。
可是本蒟蒻不甘心啊!我随口说的思路怎么可能错!于是赛后打开题解,看如痴如醉(不明所以 )就跟着题解的方法写了一遍,就一发入魂,AC了。
虽然过了,但题解中说的方法让我感到不解,为什么可以这样写呢?
思路:
如果把问题化简一下:我 开小汽车 从 天涯 到 海北 ,(其实天涯和海北在同一个地方,但这里就理解成很远很远的两个地方就可以了 ),求天涯到海北的最短路径。
但是 小汽车 的油箱是有限的,所以必须在油耗尽之间开到加油站。于是这个问题不能简单的用最短路去求,而要考虑加油站的问题。
建图不用多说,只要写法不阴间就可以了。
建图之后要跑一次Floyd,找到两点之间的最短路径,如果这两件之间的路径大于你的小汽车能跑的距离就直接赋值INF,表示这两点不可能不过加油站直线到。这样做可以简化图形。
我们再进一步考虑,天涯和海北两个地方相距那么远,我们开小汽车要经过多少个加油站啊!那么把需要经过的加油站缩小成一个点,这些点链接起来是不是就成了一条线了呢?
所以直接引出来:找最短路实际上就是找所有加油站的最小路。换句话说,就是建一个以加油站为点的图,找到一条最短路就可以了!
这时候有人会说:这不是胡搞吗?为什么一定要从加油站到加油站?老子开特斯拉,跑你个千百公里不是事情!
确实!你的特斯拉牛逼!但是天涯和海北相距十万八千里,你开特斯拉也不一定能直接跑到 ,(开个玩笑)所以,问题可以进一步转换:因为你不能一次性到达海北,所以你在途中必须经过至少一个加油站,因为在之前跑了一次Floyd,所以已经知道两个加油站之间的最短路,所以其他城市是不必要的了,(其他城市存在的目的就是找到两个加油站之间的最短路径)。即直接提取加油站建过一张图。
建完图之后这道题就是一个裸体了,直接迪杰特斯拉(斯特拉)或者Floyd跑一遍最短路就可了。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll INF=0x3f3f3f3f3f;
const int MAXN=505;
int flag[MAXN];
ll s[MAXN][MAXN];
ll g[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,m,t,d;
cin>>n>>m>>t>>d;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i==j)s[i][j]=0;else s[i][j]=INF;
for(int i=2;i<=t+1;i++)
{
cin>>g[i];
}
g[1]=1;g[t+2]=n;
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
s[v][u]=s[u][v]=w;
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i][j]>s[i][k]+s[k][j])
{
s[i][j]=s[i][k]+s[k][j];
}
}
}
}
for(int i=1;i<=t+2;i++)
{
for(int j=1;j<=t+2;j++)
{
int u=g[i],v=g[j];
if(s[u][v]>d)s[u][v]=INF;
}
}
for(int k=1;k<=t+2;k++)
{
for(int i=1;i<=t+2;i++)
{
for(int j=1;j<=t+2;j++)
{
int u=g[i],v=g[j],kk=g[k];
if(s[u][v]>s[u][kk]+s[kk][v])s[u][v]=s[u][kk]+s[kk][v];
}
}
}
if(s[1][n]==INF)cout<<"stuck"<<endl;
else cout<<s[1][n]<<endl;
return 0;
}
后记:
机缘巧合之下听到学长把迪杰斯特拉说出迪杰特斯拉,觉得莫名喜感,于是就选特斯拉作为例子了。