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[k][...][...]
的更新只用到了f[k - 1][...][...]
,那么我们可以简化成:\(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\),即\(x\to a\to b\to 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;
}