首先,我们一定要认识到本题中的最短时间所对应的道路不一定是在起点到终点的最短路。例如,起点到终点的最短路为
15
15
15 ,那么对
15
15
15 进行二进制拆分的话是
1111
1111
1111 ,这时求出的最短时间为4。然而如果有一条长度为
16
16
16 的路径的话最短时间就为
1
1
1,显然比之前求的更优 。
我们在这里定义两个数组:
- i n t int int d [ i ] [ j ] d[i][j] d[i][j],即代表点 ( i , j ) (i,j) (i,j) 之间的最短跑步时间。
-
b
o
o
l
bool
bool
g
[
i
]
[
j
]
[
k
]
g[i][j][k]
g[i][j][k],它代表的是点
(
i
,
j
)
(i,j)
(i,j) 之间是否有一条跑步时间为
2
k
2^k
2k 的一条道路。
我们对 g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k] 运行一遍 F l o y d Floyd Floyd 来更新。
更新方程是:
i f ( g [ i ] [ k ] [ l o g − 1 ] if(g[i][k][log-1] if(g[i][k][log−1] && g [ k ] [ j ] [ l o g − 1 ] ) g[k][j][log-1]) g[k][j][log−1]) g [ i ] [ j ] [ l o g ] = 1 g[i][j][log]=1 g[i][j][log]=1 , d [ i ] [ j ] = 1 d[i][j]=1 d[i][j]=1.
显然,我们处理完
g
g
g 数组后所有可以在一秒之内到达的点对都已处理完毕。于是我们在图上对
d
[
i
]
[
j
]
d[i][j]
d[i][j] 再跑一遍
F
l
o
y
d
Floyd
Floyd 即可。
时间复杂度为
O
(
n
3
l
o
g
m
)
O(n^3logm)
O(n3logm)。
#include<cstdio>
#include<algorithm>
using namespace std;
const int logn = 60;
const int maxm = 500000 + 5;
const int maxn = 60;
const int inf = 100000000;
int d[maxn][maxn];
bool g[maxn][maxn][logn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n;++i)
for(int j = 1;j <= n;++j)d[i][j] = inf;
for(int i = 1;i <= m;++i)
{
int a,b;
scanf("%d%d",&a,&b);
d[a][b] = 1, g[a][b][0] = 1;
}
for(int p = 1; p < logn; ++p)
for(int k = 1; k <= n;++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n;++j)
{
if(g[i][k][p-1] && g[k][j][p-1]) g[i][j][p] = 1, d[i][j] = 1;
}
for(int k = 1;k <= n;++k)
for(int i = 1;i <= n;++i)
for(int j = 1;j <= n;++j)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
printf("%d",d[1][n]);
return 0;
}