Floyd + 矩阵快速幂 + 离散化 - Cow Relays - POJ 3613
题意:
给定一张由T条边构成的无向图,点的编号为1~1000之间的整数。
求从起点S到终点E恰好经过N条边(可以重复经过)的最短路。
注意: 数据保证一定有解。
输入格式
第1行:包含四个整数N,T,S,E。
第2…T+1行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。
输出格式
输出一个整数,表示最短路的长度。
数据范围
2 ≤ T ≤ 100 , 2 ≤ N ≤ 1 0 6 2≤T≤100, 2≤N≤10^6 2≤T≤100,2≤N≤106
输入样例:
2 6 6 4
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9
输出样例:
10
分析:
我 们 改 变 一 下 f l o y d 算 法 中 的 d 数 组 的 含 义 : 我们改变一下floyd算法中的d数组的含义: 我们改变一下floyd算法中的d数组的含义:
d [ k ] [ i ] [ j ] : 表 示 从 i 到 j , 且 经 过 恰 好 k 条 边 的 最 短 距 离 。 d[k][i][j]:表示从i到j,且经过恰好k条边的最短距离。 d[k][i][j]:表示从i到j,且经过恰好k条边的最短距离。
假 设 k = a + b , 即 k 条 边 分 为 两 部 分 , 前 一 部 分 为 a 条 边 , 后 一 部 分 为 b 条 边 , 第 a + 1 个 点 设 为 点 s 。 假设k=a+b,即k条边分为两部分,前一部分为a条边,后一部分为b条边,第a+1个点设为点s。 假设k=a+b,即k条边分为两部分,前一部分为a条边,后一部分为b条边,第a+1个点设为点s。
则 d [ k ] [ i ] [ j ] = m i n ( d [ k ] [ i ] [ j ] , d [ a ] [ i ] [ s ] + d [ b ] [ s ] [ j ] ) 。 则d[k][i][j]=min(d[k][i][j],d[a][i][s]+d[b][s][j])。 则d[k][i][j]=min(d[k][i][j],d[a][i][s]+d[b][s][j])。
若 我 们 每 次 增 加 1 条 边 计 算 最 短 路 径 , 最 多 1 0 6 条 边 , 每 次 f l o y d 要 O ( n 3 ) , 会 超 时 。 若我们每次增加1条边计算最短路径,最多10^6条边,每次floyd要O(n^3),会超时。 若我们每次增加1条边计算最短路径,最多106条边,每次floyd要O(n3),会超时。
但 是 由 于 最 短 路 的 性 质 : 一 分 为 二 , 两 相 互 独 立 , 分 别 计 算 最 短 路 再 相 加 。 但是由于最短路的性质:一分为二,两相互独立,分别计算最短路再相加。 但是由于最短路的性质:一分为二,两相互独立,分别计算最短路再相加。
故 我 们 可 以 类 比 到 矩 阵 乘 法 中 倍 增 的 思 想 , 利 用 矩 阵 快 速 幂 来 计 算 l o g 2 m 次 即 可 。 故我们可以类比到矩阵乘法中倍增的思想,利用矩阵快速幂来计算log_2m次即可。 故我们可以类比到矩阵乘法中倍增的思想,利用矩阵快速幂来计算log2m次即可。
时 间 复 杂 度 降 到 O ( n 3 l o g 2 m ) 。 时间复杂度降到O(n^3log_2m)。 时间复杂度降到O(n3log2m)。
这 就 告 诉 我 们 , f l o y d 算 法 可 以 扩 展 求 经 过 恰 好 k 条 边 的 最 短 路 , 时 间 复 杂 度 是 O ( n 3 l o g 2 m ) 。 这就告诉我们,floyd算法可以扩展求经过恰好k条边的最短路,时间复杂度是O(n^3log_2m)。 这就告诉我们,floyd算法可以扩展求经过恰好k条边的最短路,时间复杂度是O(n3log2m)。
问题:如何逐次增加边的数量来更新最短路?
我 们 用 二 维 数 组 r e s 来 保 存 经 过 恰 好 k 条 边 的 任 意 两 点 间 的 最 短 距 离 。 我们用二维数组res来保存经过恰好k条边的任意两点间的最短距离。 我们用二维数组res来保存经过恰好k条边的任意两点间的最短距离。
初 始 化 r e s 为 i n f , r e s [ i ] [ i ] = 0 , 表 示 恰 好 经 过 0 条 边 的 任 意 两 点 间 的 最 短 距 离 。 初始化res为inf,res[i][i]=0,表示恰好经过0条边的任意两点间的最短距离。 初始化res为inf,res[i][i]=0,表示恰好经过0条边的任意两点间的最短距离。
接 着 我 们 用 输 入 的 邻 接 矩 阵 更 新 r e s 数 组 , 借 鉴 快 速 幂 倍 增 的 思 想 来 更 新 k 次 即 可 。 接着我们用输入的邻接矩阵更新res数组,借鉴快速幂倍增的思想来更新k次即可。 接着我们用输入的邻接矩阵更新res数组,借鉴快速幂倍增的思想来更新k次即可。
注意:
初 始 化 邻 接 矩 阵 g 时 , 不 能 初 始 化 g [ i ] [ i ] = 0 , 因 为 这 表 示 存 在 边 权 为 0 的 自 环 , 事 实 上 是 没 有 的 。 初始化邻接矩阵g时,不能初始化g[i][i]=0,因为这表示存在边权为0的自环,事实上是没有的。 初始化邻接矩阵g时,不能初始化g[i][i]=0,因为这表示存在边权为0的自环,事实上是没有的。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int N=110;
int n,m,k,S,E;
int g[N][N];
int res[N][N];
map<int,int> M;
void mul(int a[][N],int b[][N])
{
int c[N][N];
memset(c,0x3f,sizeof c);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
memcpy(a,c,sizeof c);
}
void quick_pow()
{
memset(res,0x3f,sizeof res);
for(int i=1;i<=n;i++) res[i][i]=0; //初始经过0条边
while(k)
{
if(k&1) mul(res,g);
mul(g,g);
k>>=1;
}
}
int main()
{
cin>>k>>m>>S>>E;
M[S]=++n,M[E]=++n;
S=M[S],E=M[E];
memset(g,0x3f,sizeof g); //g的含义是经过一条边的距离,故不能初始化g[i][i]=0,因为不存在权为0的自环
int a,b,c;
while(m--)
{
cin>>c>>a>>b;
if(!M.count(a)) M[a]=++n;
if(!M.count(b)) M[b]=++n;
a=M[a],b=M[b];
g[a][b]=g[b][a]=min(g[a][b],c);
}
quick_pow();
cout<<res[S][E]<<endl;
return 0;
}