Floyd演算法小結(複習)
Floyd演算法是從集合的角度出發,在\(O(n^3)\)的複雜度內,求出圖中任意兩點的最短距離
令\(f(k,i,j)\)表示成經過點\(k\)後,\(i\),\(j\)之間的最短距離
那麼就有轉移:\(f(k,i,j)=min(f(k-1,i,k)+f(k-1,k,j))\)
隨後可以發現,這樣只是用到了上個狀態,那麼我們可以簡化成:\(f(i,j)\),表示\(i,j\)之間最短的距離
常用於:
-
最短路
-
傳遞閉包
-
找最小環
-
恰好經過\(k\)條邊的最短路
1125. 牛的旅行 - AcWing題庫
直徑為一個聯通塊內任意兩點最短路中最長的距離
本題給你兩個聯通塊,讓你在兩個聯通塊各選任意一點,搭建一條邊使得兩個聯通塊合併,求新的聯通塊中的直徑最小是多少
新聯通塊中的直徑有以下幾種可能:
-
原想兩個聯通塊中較大的直徑,在合併後依然是新聯通塊中的直徑
-
連線的兩個點間的距離\(dis\)加上在各自聯通塊內能到達的距離\(d_a, d_b\):\(dis+d_a+d_b\)
所以選以上兩種情況的最大值即為答案
#include <iostream> #include <algorithm> #include <cmath> using namespace std; constexpr int N = 200; constexpr double INF = 1e18; typedef pair<int, int> PII; int n; PII q[N]; double d[N][N], maxd[N]; double f(int x, int y) { double a = (q[x].first - q[y].first), b = (q[x].second - q[y].second); return sqrt(a * a + b * b); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { int a, b; scanf("%d%d", &a, &b); q[i] = {a, b}; } for (int i = 1; i <= n; i++) { static char s[N]; scanf("%s", s + 1); for (int j = 1; j <= n; j++) { if (s[j] == '1' || i == j) d[i][j] = f(i, j); else d[i][j] = INF; } } 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]); } } } double A = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (d[i][j] < INF) maxd[i] = max(maxd[i], d[i][j]); } A = max(maxd[i], A); } double B = INF; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if (d[i][j] < INF || i == j) continue; B = min(B, maxd[i] + maxd[j] + f(i, j)); } printf("%.6lf\n", max(A, B)); return 0; }
343. 排序 - AcWing題庫
本題給出關於\(n\)個字元的\(m\)個不等式關係,問你能否確定兩兩之間的關係或判斷有無矛盾
由於不等式存在傳遞性,那麼不同字母間的不等式關係可以抽象成一張由較小字母指向較大字母的有向圖
對於互不相等的字元\(A, B, C\),如果\(A<B\)且\(B<C\),那麼我們也就間接確定了\(A<C\),
而這個過程就是圖的傳遞閉包
。
所以在建圖的過程中,求出圖的傳遞閉包,當閉包中任意兩點之間都標記過,那麼代表
\(n\)個點之間兩兩存在關係。
當某個點和自己存在不等式關係時,代表存在矛盾
當\(m\)個不等式關係結束後還不能確定關係,說明不存在關係
用Floyd演算法來傳遞閉包,是最樸素的做法
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 30;
int n, m;
bool st[N], g[N][N], d[N][N];
inline void floyd() {
memcpy(d, g, sizeof d);
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
d[i][j] |= d[i][k] and d[k][j];
}
inline int check() {
for (int i = 0; i < n; i++)
if (d[i][i]) return 2;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
if (i != j and !d[i][j] and !d[j][i]) return 0;
}
return 1;
}
inline char get_min() {
for (int i = 0; i < n; i++) {
if (!st[i]) {
bool ok = 1;
for (int j = 0; j < n and ok; j++) {
if (!st[j] and d[j][i]) {
ok = 0;
}
}
if (ok) {
st[i] = true;
return 'A' + i;
}
}
}
return 'A';
}
int main() {
while (~scanf("%d%d", &n, &m) and n and m) {
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);
int type = 0, u = 0;
for (int i = 1; i <= m; i++) {
char a, b;
scanf(" %c<%c", &a, &b);
a -= 'A', b -= 'A';
if (!type) {
g[a][b] = 1;
floyd();
type = check();
if (type) u = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");
else if (type == 2) {
printf("Inconsistency found after %d relations.\n", u);
} else {
printf("Sorted sequence determined after %d relations: ", u);
for (int i = 0; i < n; i++) putchar(get_min());
printf(".\n");
}
}
return 0;
}
優化
我們發現在本題中閉包的傳遞可以通過列舉能到達a的起點x和b能到達的重點y,
當a能到達b時:
-
如果存在起點x到達a和b能到達終點y,那麼x能到y
-
如果點x能到達點a,同樣也能到點b,
-
如果b能到達x,那麼a也能到達x
這樣也能完成閉包的傳遞,從\(O(n^3)\)的時間複雜度降低到\(O(n^2)\)
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 30;
int n, m;
bool st[N], g[N][N];
inline int check() {
for (int i = 0; i < n; i++)
if (g[i][i]) return 2;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
if (i != j and !g[i][j] and !g[j][i]) return 0;
}
return 1;
}
inline char get_min() {
for (int i = 0; i < n; i++) {
if (!st[i]) {
bool ok = 1;
for (int j = 0; j < n and ok; j++) {
if (!st[j] and g[j][i]) {
ok = 0;
}
}
if (ok) {
st[i] = true;
return 'A' + i;
}
}
}
return 'A';
}
int main() {
while (~scanf("%d%d", &n, &m) and n and m) {
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);
int type = 0, u = 0;
for (int i = 1; i <= m; i++) {
char a, b;
scanf(" %c<%c", &a, &b);
a -= 'A', b -= 'A';
if (!type) {
g[a][b] = 1;
for (int x = 0; x < n; x++) {
if (g[x][a]) g[x][b] = 1;
if (g[b][x]) g[a][x] = 1;
for (int y = 0; y < n; y++) {
if (g[x][a] and g[b][y]) g[x][y] = 1;
}
}
type = check();
if (type) u = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");
else if (type == 2) {
printf("Inconsistency found after %d relations.\n", u);
} else {
printf("Sorted sequence determined after %d relations: ", u);
for (int i = 0; i < n; i++) putchar(get_min());
printf(".\n");
}
}
return 0;
}
344. 觀光之旅 - AcWing題庫
本題是求最小環的路徑
求最小環
對於求最小環,我們需要對Floyd演算法做出一點小變形:
在Floyd演算法中,當最外層迴圈到點\(k\)時,最短路陣列中f[i,j]
表示\(i\)和\(j\)在\([1,k-1]\)的最短路徑長度。
由最小環的定義可以知道,一個環至少有3個定點,當外層迴圈列舉到\(k\)時,該環的長度為\(dis(i,j)+f[j,k]+f[k,i]\)。這樣就可以求出最小環的長度
求路徑:
最短路徑的組成是\(f(i,k)+f(k,j)\),當一個最短路徑\(f(i,j)\)發生更新時,那麼此時的路徑為\(i\to k\to j\),所以在求最短路的時候,要記錄下來中間節點。
隨後在求最小環的時候,通過遞迴來求出路徑
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
constexpr int N = 210, INF = 0x3f3f3f3f;
int g[N][N], d[N][N], n, m;
int middle[N][N], path[N], cnt;
void get_path(int i, int j) {
if (middle[i][j] == 0) return;
int k = middle[i][j];
get_path(i, k);
path[cnt++] = k;
get_path(k, j);
}
int main() {
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) g[i][i] = 0;
while (m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int res = INF;
memcpy(d, g, sizeof g);
for (int k = 1; k <= n; k++) {
for (int i = 1; i < k; i++)
for (int j = i + 1; j < k; j++) {
if ((LL)d[i][j] + g[j][k] + g[k][i] < res) {
res = d[i][j] + g[j][k] + g[k][i];
cnt = 0;
path[cnt++] = i;
get_path(i, j);
path[cnt++] = j;
path[cnt++] = k;
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
if (d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
middle[i][j] = k;
}
}
}
if (cnt) {
for (int i = 0; i < cnt; i++)
printf("%d ", path[i]);
} else {
puts("No solution.");
}
return 0;
}
345. 牛站 - AcWing題庫
本題可以將狀態表示為\(f(k,i,j)\):經過\(k\)條邊後\(i\),\(j\)之間的最短路徑
那麼就有轉移\(f(a+b,i,j)=f(a,i,k)+f(b,k,j)\)
也就是說,對於經過\(a+b\)條邊的從\(i\)到\(j\)的最短路z[i][j]
,是經過\(a\)條邊從\(i\)到\(k\)的最短路x[i][k]
和經過\(b\)條邊從\(k\)到\(j\)的最短路y[k][j]
,即z[i][k] = min(x[i][k] + y[k][j])
,且這幾個狀態可以看作是相互獨立的。
所以\(f(k,i,j)\)就等於\(k\)個\(f(1,i,j)\)相加起來,由於圖表示在矩陣中,那麼我們可以用矩陣快速冪,快速的求出\(k\)個\(f(1,i,j)\)相加的結果
注意:
-
本題每個點會出現多次,且會有重遍,所以需要對點進行離散化並去最小邊
-
記得給
matrix[i][i]
初始化成0,因為自己走到自己距離是0
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 210;
unordered_map<int, int> id;
int k, t, S, E, n;
int g[N][N], res[N][N];
inline void mul(int c[][N], int a[][N], int b[][N]) {
static int tmp[N][N];
memset(tmp, 0x3f, sizeof tmp);
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
tmp[i][j] = min(tmp[i][j], a[i][k] + b[k][j]);
}
memcpy(c, tmp, sizeof tmp);
}
inline void fpow() {
memset(res, 0x3f, sizeof res);
// 設定單位元
for (int i = 1; i <= n; i++) res[i][i] = 0;
while (k) {
if (k & 1) mul(res, res, g);
mul(g, g, g);
k >>= 1;
}
}
int main() {
scanf("%d%d%d%d", &k, &t, &S, &E);
id[S] = ++n;
id[E] = ++n;
S = id[S];
E = id[E];
memset(g, 0x3f, sizeof g);
while (t --) {
int a, b, c;
scanf("%d%d%d", &c, &a, &b);
if (!id.count(a)) id[a] = ++n;
if (!id.count(b)) id[b] = ++n;
a = id[a], b = id[b];
g[a][b] = g[b][a] = min(g[a][b], c);
}
fpow();
printf("%d\n", res[S][E]);
return 0;
}