HihoCoder-1089
vjudge
小Ho道:“你说的很有道理,我只需要从每个节点开始使用Dijstra算法就可以了!”
小Hi摇摇头道:“解决问题不是关键,学到知识才是关键,而且知识本身也远远没有掌握学习的方法重要!”
小Ho只得答道:“好的好的,听你说便是了!”
于是小Hi便开心道:“这次要说的算法叫做Floyd算法,是一种用于求图结构上任意两点间最短距离的算法!”
小Ho嘀咕道:“你都写标题上了,能不知道么?”
小Hi强行装作没听到,继续说道:“这个算法的核心之处在于数学归纳法——MinDistance(i, j)之间最短路径中可以用到的节点是一点点增加的!”
“你这话每一个字我都听得懂,但是这句话为什么我听不懂呢……”小Ho无奈道。
“那我这么说吧,首先,最开始的时候,MinDistance(i, j)——即从第i个点到第j个点的最短路径的长度,拥有一个限制:这条路径不能经过任何节点。”小Hi道。
“那就是说如果从i个点到第j个点之间没有直接相连的边的话,这个长度就是无穷大咯?”小Ho总结道:“只需要把输入的边填进MinDistance中即可!”
“对!”小Hi满意于小Ho的上道,继续说道:“然后我放开限制,我允许MinDistance(i, j)——从第i个点到第j个点的最短路径的长度,拥有的限制,变为:这条路径仅允许经过1号节点。”
“这个也简单,对于两个节点i, j,我只需要比较MinDistance(i, j)原来的值和MinDistance(i, 1)+MinDistance(1, j)的值,取较小的一个作为新的MinDistance(i, j)就可以了——毕竟原来的MinDistance都是不经过任何节点,那么这样求出来的新的MinDistance(i, j)只有可能经过1号节点。”
“那么接下来就是关键的了,我将限制继续放宽——路径仅允许经过1、2号节点。”小Hi继续说道。
“那其实也没有任何变化吧,对于两个节点i, j,我只需要比较MinDistance(i, j)原来的值和MinDistance(i, 2)+MinDistance(2, j)的值,取较小的一个作为新的MinDistance(i, j),之所以可以这样是因为,原来的MinDistance都是在限制“仅允许经过1号节点”下,求出来的,所以新求出来的MinDistance(i, j)也只有可能经过1、2号节点!“
“那我继续放开限制呢?”小Hi问道。
“也没有什么区别了,每放开一个新的节点k允许作为路径中的节点,就对于任意的i, j,用MinDistance(i, k)+MinDistance(k, j)去更新MinDistance(i, j),直到1…N号节点都被添加进限制,此时也就等于没有限制了,那么这个时候的MinDistance(i, j)就是我们所想要求的值,写成伪代码就是这样!”
for k = 1 .. N
for i = 1 .. N
for j = 1 .. N
若i, j, k各不相同
MinDistance[i, j] = min{MinDistance[i, j], MinDistance[i, k] + MinDistance[k, j]}
“看来你已经很明白了呢!”小Hi嘿嘿一笑,往鬼屋深处跑了去——那么接下来就是小Ho利用求出的最短路径,去找到小Hi的时候了!
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1005;
const int inf=0x3f3f3f;
int n,m;
int f[N][N];
int main() {
scanf("%d%d",&n,&m);
memset(f,inf,sizeof(f));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
f[u][v]=MIN(f[u][v],w);
f[v][u]=MIN(f[v][u],w);
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
f[i][j]=MIN(f[i][j],f[i][k]+f[k][j]);
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++)
printf("%d ",(i==j) ? 0 : f[i][j]);
printf("\n");
}
return 0;
}
HihoCoder-1089
vjudge
小Ho想了想说道:“唔……我觉得动态规划可以做,但是我找不到计算的顺序,如果我用f[i]表示从S到达编号为i的节点的最短距离的话,我并不能够知道f[1]…f[N]的计算顺序。”
“所以这个问题不需要那么复杂的算法啦,我就稍微讲讲你就知道了!”小Hi道:“路的长度不可能为负数对不对?”
“那是自然,毕竟人类还没有发明时光机器……”小Ho点点头。
于是小Hi问道:“那么如果就看与S相邻的所有节点中与S最近的那一个S’,并且从S到S’的距离为L,那么有可能存在另外的道路使得从S到S’的距离小于L么?”
“不能,因为S’是与S相邻的所有节点中与S最近的节点,那么从S到其他相邻点的距离一定是不小于L的,也就是说无论接下来怎么走,回到L点时总距离一定大于L。”小Ho思考了一会,道。
“也就是说你已经知道了从S到S’的最短路径了是么?”小Hi继续问道。
“是的,这条最短路径的长度是L。”小Ho答道。
小Hi继续道:“那么现在,我们不妨将S同S’看做一个新的节点?称作S1,然后我就计算与S相邻或者与S’相邻的所有节点中,与S最近的哪一个节点S’’。注意,在这个过程中,与S相邻的节点与S的距离在上一步就已经求出来了,那么我要求的只有与S’相邻的那些节点与S的距离——这个距离等于S与S’的距离加上S’与这些结点的距离,对于其中重复的节点——同时与S和S’相邻的节点,取两条路径中的较小值。”
小Ho点了点头:“那么同之前一样,与S1(即S与S’节点)相邻的节点中与S’距离最近的节点如果是S’‘的话,并且这个距离是L2,那么我们可以知道S到S’'的最短路径的长度便是L2,因为不可能存在另外的道路比这个更短了。”
于是小Hi总结道:“接下来的问题不就很简单了么,只需要以此类推,每次将与当前集合相邻(即与当前集合中任意一个元素)的所有节点中离S最近的节点(这些距离可以通过上一次的计算结果推导而出)选出来添加到当前集合中,我就能够保证在每一个节点被添加到集合中时所计算的离S的距离是它与S之间的最短路径!”
“原来是这样!但是我的肚子更饿了呢!”言罢,小Ho的肚子咕咕叫了起来。
Dijkstra:
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1005;
const int inf=0x3f3f3f;
int n,m,s,t;
int d[N],vis[N];
int a[N][N];
void dijkstra() {
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) d[i]=inf;
d[s]=0;
for(int i=1; i<=n; i++) {
int k=-1;
for(int j=1; j<=n; j++)
if(!vis[j] && (k==-1 || d[k]>d[j])) k=j;
vis[k]=1;
for(int j=1; j<=n; j++)
if(!vis[j] && d[k]+a[k][j]<d[j])
d[j]=d[k]+a[k][j];
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(a,inf,sizeof(a));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[u][v]=MIN(a[u][v],w);
a[v][u]=MIN(a[v][u],w);
}
dijkstra();
printf("%d\n",d[t]==inf ? -1 : d[t]);
return 0;
}
堆优化Dijkstra:
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#include
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1005;
const int M=10005;
const int inf=0x3f3f3f;
struct Edge {
int to,nt,w;
} e[M<<1];
struct Node {
int index,dist;
bool operator < (const Node &rhs) const {
return dist>rhs.dist;
}
};
int n,m,s,t,cnt;
int d[N],vis[N],h[N];
priority_queue<Node> q;
inline void add(int u,int v,int w) {
e[++cnt].to=v;
e[cnt].nt=h[u];
e[cnt].w=w;
h[u]=cnt;
}
void dijkstra() {
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) d[i]=inf;
d[s]=0;
q.push((Node){s,0});
while(!q.empty()) {
Node x=q.top();
q.pop();
int u=x.index;
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u]; i; i=e[i].nt) {
int v=e[i].to;
if(d[v]>d[u]+e[i].w) {
d[v]=d[u]+e[i].w;
q.push((Node){v,d[v]});
}
}
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dijkstra();
printf("%d\n",d[t]==inf ? -1 : d[t]);
return 0;
}
HihoCoder-1089
vjudge
“唔……地点很多,道路很少,这个鬼屋是一个稀疏图,既然这一点被特地标注出来,那么想来有其作用的咯?”小Ho道。
“是的,正好有一种最短路径算法,它的时间复杂度只和边的条数有关,所以特别适合用来解决这种边的数量很少的最短路问题!”小Hi点了点头道:“它就是SPFA算法,即Shortest Path Faster Algorithm。”
“听上去很厉害的样子,但是实际上怎么做的呢?”小Ho问道。
“你会用宽度优先搜索写这道题么?”小Hi反问道。
“这个当然会啊,构造一个队列,最开始队列里只有(S, 0)——表示当前处于点S,从点S到达该点的距离为0,然后每次从队首取出一个节点(i, L)——表示当前处于点i,从点S到达该点的距离为L,接下来遍历所有从这个节点出发的边(i, j, l)——表示i和j之间有一条长度为l的边,将(j, L+l)加入到队尾,最后看所有遍历的(T, X)节点中X的最小值就是答案咯~”小Ho对于搜索已经是熟稔于心,张口便道。
“SPFA算法呢,其实某种意义上就是宽度优先搜索的优化——如果你在尝试将(p, q)加入到队尾的时候,发现队列中已经存在一个(p, q’)了,那么你就可以比较q和q’:如果q>=q’,那么(p, q)这个节点实际上是没有继续搜索下去的必要的——算是一种最优化剪枝吧。而如果q<q’,那么(p, q’)也是没有必要继续搜索下去的——但是它已经存在于队列里了怎么办呢?很简单,将队列中的(p, q’)改成(p, q)就可以了!”
“那我该怎么知道队列中是不是存在一个(p, q’)呢?” <额,维护一个position[1…n]的数组就可以了,如果不在队列里就是-1,否则就是所在的位置!”< p>
“所以说这本质上就是宽度优先搜索的剪枝咯?”小Ho问道。
小Hi笑道:“怎么可能!SPFA算法其实是BELLMAN-FORD算法的一种优化版本,只不过在成型之后可以被理解成为宽度优先搜索的!这个问题,我们会在之后好好讲一讲的!”
用vector实现。
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#include
#include
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int inf=1e9;
int n,m,s,t;
int v[N],d[N];
vector<int> a[N],b[N];
queue<int> q;
int spfa() {
for(int i=1; i<=n; i++) d[i]=inf;
q.push(s);
v[s]=1;
d[s]=0;
while(!q.empty()) {
int x=q.front();
q.pop();
v[x]=0;
for(int i=0; i<a[x].size(); i++) {
int tp=a[x][i];
if(d[tp]>d[x]+b[x][i]) {
d[tp]=d[x]+b[x][i];
if(!v[tp]) {
q.push(tp);
v[tp]=1;
}
}
}
}
if(d[t]==inf) d[t]=-1;
return d[t];
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[u].push_back(v);
b[u].push_back(w);
a[v].push_back(u);
b[v].push_back(w);
}
printf("%d\n",spfa());
return 0;
}
用链式前向星实现。
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#include
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int M=1e6+5;
const int inf=1e9;
struct Edge {
int to,nt,w;
} e[M<<1];
int n,m,s,t,cnt;
int v[N],d[N],h[N];
queue<int> q;
inline void add(int a,int b,int c) {
e[++cnt]=(Edge){b,h[a],c};
h[a]=cnt;
}
int spfa() {
for(int i=1; i<=n; i++) d[i]=inf;
q.push(s);
v[s]=1;
d[s]=0;
while(!q.empty()) {
int x=q.front();
q.pop();
v[x]=0;
for(int i=h[x]; i; i=e[i].nt) {
int tp=e[i].to;
if(d[tp]>d[x]+e[i].w) {
d[tp]=d[x]+e[i].w;
if(!v[tp]) {
q.push(tp);
v[tp]=1;
}
}
}
}
if(d[t]==inf) d[t]=-1;
return d[t];
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(h,-1,sizeof(h));
for(int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
printf("%d\n",spfa());
return 0;
}