345. 牛站(Floyd倍增算法,Floyd快速幂)
给定一张由 T 条边构成的无向图,点的编号为 1∼1000 之间的整数。
求从起点 S 到终点 E 恰好经过 N 条边(可以重复经过)的最短路。
注意: 数据保证一定有解。
输入格式
第 1 行:包含四个整数 N,T,S,E。
第 2..T+1 行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。
输出格式
输出一个整数,表示最短路的长度。
数据范围
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
解析:
Floyd的倍增算法的经典运用:
第一步:离散化
第二步:
Floyd算法中d[k,i,j]表示:从i到j经过的点的编号不大于k的最短路径。
Floyd的倍增算法中d[k,i,j]表示:从i到j,恰好经过k条边的最短路径。
状态转移方程:
d[a+b,i,j]=min{d[a,i,k]+d[b,k,j]}, k=1~n
直接按照上述过程进行求解的话我们需要四个循环,反别枚举d[k,i,j]中的三个变量,还有一个循环用于枚举中间点,将k一分为二。
但这里我们可以考虑使用倍增(或者叫快速幂)的方式进行处理,因为其运算过程不考虑先后顺序(满足结合律)。
时间复杂度为O(n^3*log2(N))
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
using namespace std;
typedef long long LL;
const int N = 205;
int n, T, S, E;
int g[N][N];
map<int, int>mp;
int ret[N][N];
int cnt;
void mul(int c[][N], int a[][N], int b[][N]) {
static int temp[N][N];
memset(temp, 0x3f, sizeof temp);
for (int k = 1; k <= cnt; k++) {
for (int i = 1; i <= cnt; i++) {
for (int j = 1; j <= cnt; j++) {
temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
}
}
}
memcpy(c, temp, sizeof temp);
}
void qmi() {
memset(ret, 0x3f, sizeof ret);
for (int i = 1; i < N; i++)ret[i][i] = 0;
while (n) {
if (n & 1)mul(ret, ret, g);
mul(g, g, g);
n >>= 1;
}
}
int main() {
cin >> n >> T >> S >> E;
memset(g, 0x3f, sizeof g);
for (int i = 1,a,b,c; i <= T; i++) {
scanf("%d%d%d", &c, &a, &b);
if (!mp.count(a))mp[a] = ++cnt;
if (!mp.count(b))mp[b] = ++cnt;
g[mp[a]][mp[b]] = g[mp[b]][mp[a]] = min(g[mp[a]][mp[b]], c);
}
qmi();
cout << ret[mp[S]][mp[E]] << endl;
return 0;
}