题目链接:https://nanti.jisuanke.com/t/A1108 (百度地图的实时路况)
问题描述
给定 n 个点的有向图。定义 d(u, v, w) 为从 u 到 w 严格不经过
v 的最短路,若路径不存在,则 d(u, v, w) = -1,求所有d(u,v,w)的和。(n<=300,1<=u,v,w<=n)
问题分析
- Floyd算法是将每个点k依次加入路径中进行计算最短路,但是与添加顺序无关。题目中是求依次去掉一个点时其它任意两点最短路之和,朴素的想法是暴力枚举每个点,用Floyd算法求去掉这一点后的最短路径。显然,这种方法的时间复杂度是O(n^4)
$\to$
10^8,必定超时。 - 经过分析,可以看出暴力枚举中存在大量重复计算。比如对于图[1…4],挖去1时,需要计算的有{2,3,4};挖去2时,需要计算的有{1,34}。其中,{3,4}的计算对于1、2来说是重复的。
- Floyd的计算结果总是叠加在原图上的,想要传递经过点集S计算过的Floyd结果,就得把计算后的图传递。
- 为了达到优化目的,使用分治的思想,伪代码如下:
//solve(l,r,F)得到的是挖去[l..r]这些点得到的多源最短路径
void solve(l,r,F){
if(l==r)//单点区间,即只挖去这一点l(r)
sum_ans()
P=F//继承父区间的Floyd结果
floyd(l,mid,P)//利用其余点(左区间)来做Floyd优化
solve(mid+1,r,P)//利用右区间Floyd结果来求解右区间问题
P=F//继承父区间的Floyd结果
floyd(mid+1,r,P)//利用其余点(右区间)来做Floyd优化
solve(l,mid,P)//利用左区间Floyd结果来求解右区间问题
}
- 原始图中节点为[1…8],对分治做出图示如下:
- 图中,以最下角的节点为例说明数字含义
- 1代表该节点所示区间为1(特别地,这是个单点区间),表示该区间被排除在外
- 5 6 7 8 / 3 4 // 2表示计算区间,其中//左边的是通过分治继承来的,//右边是当前位置计算的
AC代码
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=300+10,INF=0x3f3f3f3f;
struct Floyd
{
int graph[maxn][maxn];
Floyd(){memset(graph,INF,sizeof(graph));}
int* operator[](int i){return graph[i];}
};
int n;
long long ans=0;
void solve(int l,int r,Floyd & F)
{
if(l==r){
for(int i=1;i<=n;i++){
if(i!=l)for(int j=1;j<=n;j++){
if(j!=l)ans+= F[i][j]==INF?-1:F[i][j];
}
}
return;
}
int mid=l+((r-l)>>2);
Floyd P=F;
for(int k=l;k<=mid;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
P[i][j]=min(P[i][j],P[i][k]+P[k][j]);
solve(mid+1,r,P);
P=F;//承接父区间的Floyd结果
for(int k=mid+1;k<=r;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
P[i][j]=min(P[i][j],P[i][k]+P[k][j]);
solve(l,mid,P);
}
int main()
{
Floyd mp;
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&mp[i][j]);
if(mp[i][j]==-1)mp[i][j]=INF;
}
}
solve(1,n,mp);
printf("%lld\n",ans);
return 0;
}
参考博客
- https://blog.csdn.net/vectorxj/article/details/70943539 (代码参考)
- https://blog.csdn.net/aircattle/article/details/52291488 (分析参考)