总览
floyd 原理
AcWing 1140. 最短网络
分析
注意分析题意: 直径最大的牧区连接后直径尽可能短
因此:
1.需要求每个牧场最短距离, 然后计算距离i点的maxd[i]表示当前牧区的直径,
2. 然后计算连接i ~ j 后, 按照题意, 让长度尽可能取短 min(maxd[i] + d[i][j] + maxd[j])
,因为经过i~j这条边不一定是直径, 因此不一定比maxd[i]大.
所以会有以下情况
3. 与情况1 取max
表示连接后的直径
code
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 155;
const double INF = 1e20;
int n;
PDD q[N];
double d[N][N];
double maxd[N];
char g[N][N];
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
for (int i = 0; i < n; i ++ ) cin >> g[i];
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (i == j) d[i][j] = 0;
else if (g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);
else d[i][j] = INF;
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
double r1 = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
if (d[i][j] < INF / 2)
maxd[i] = max(maxd[i], d[i][j]);
r1 = max(r1, maxd[i]); // 计算直径
}
double r2 = INF;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (d[i][j] > INF / 2)
r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j])); // 直径最大的两个牧场,
// 的经过i~j的最小距离
printf("%.6lf\n", max(r1, r2)); // 直径最大的牧场连接i~j的后的直径
return 0;
}
传递闭包
a -> b, b->c, 那么我们可以连接a->c, 所有可以间接到达的点, 我们可以连接一条直接的边, 然后把所有这些间接能到的点连上边后, 所形成的图叫做原图的传递闭包
flyod 可以在 O ( n 3 ) O(n^3) O(n3) 将原图 变成传递闭包, 即:g(i, j) -> d(i, j)
AcWing 343. 排序
分析
读入当前组的数据, 对当前组的数据, 求一遍传递闭包.
求完传递闭包, 判断下3种情况
输出次序: 从0开始遍历, 求出当前最小的, 且没有被标记过的数, 并且打上标记.
code
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 26;
int n, m;
bool g[N][N], d[N][N];
bool st[N];
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] && d[k][j]; // 注意这边是 |=
}
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 < i; j ++ )
if (!d[i][j] && !d[j][i])
return 0; // 表示关系不确定
return 1; // 表示关系确定
}
char get_min()
{
for (int i = 0; i < n; i ++ )
if (!st[i])
{
bool flag = true;
for (int j = 0; j < n; j ++ )
if (!st[j] && d[j][i]) // 存在一个没有被标记过, 且小于i的元素, 说明当前的i不符合要求
{
flag = false;
break;
}
if (flag)
{
st[i] = true;
return 'A' + i;
}
}
}
int main()
{
while (cin >> n >> m, n || m)
{
memset(g, 0, sizeof g);
int type = 0, t;
for (int i = 1; i <= m; i ++ )
{
char str[5];
cin >> str;
int a = str[0] - 'A', b = str[2] - 'A';
if (!type) // 只有当前状态不确定type == 0的时候, 才能继续往后做
{
g[a][b] = 1;
floyd();
type = check();
if (type) t = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");
else if (type == 2) printf("Inconsistency found after %d relations.\n", t);
else
{
memset(st, 0, sizeof st);
printf("Sorted sequence determined after %d relations: ", t);
for (int i = 0; i < n; i ++ ) printf("%c", get_min());
printf(".\n");
}
}
return 0;
}
code(增量算法)
因为每次只会增加1条边, 因此只需要将这条边能够连接到的边, 增加上去即可, 比如加了一条边 i → j i \to j i→j, 不需要重新做一遍floyd
只需要看下由这条边, 可以传递到的所有的关系
枚举下, 如果 a → i , j → b a \to i, j\to b a→i,j→b, 那么 a → b a \to b a→b
时间复杂度 O ( n 3 ) → O ( n 2 ) O(n^3) \to O(n^2) O(n3)→O(n2)
注意: 增加这条边的时候, 同时更新 前面的点 能到b的边, 即:
if (d[x][a]) d[x][b] = 1
反过来也是一样
同时也要更新a
能到的边
if (d[b][x]) d[a][x] = 1
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 26;
int n, m;
bool d[N][N];
bool st[N];
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 < i; j ++ )
if (!d[i][j] && !d[j][i])
return 0;
return 1;
}
char get_min()
{
for (int i = 0; i < n; i ++ )
if (!st[i])
{
bool flag = true;
for (int j = 0; j < n; j ++ )
if (!st[j] && d[j][i])
{
flag = false;
break;
}
if (flag)
{
st[i] = true;
return 'A' + i;
}
}
}
int main()
{
while (cin >> n >> m, n || m)
{
memset(d, 0, sizeof d);
int type = 0, t;
for (int i = 1; i <= m; i ++ )
{
char str[5];
cin >> str;
int a = str[0] - 'A', b = str[2] - 'A';
if (!type)
{
d[a][b] = 1;
for (int x = 0; x < n; x ++ )
{
if (d[x][a]) d[x][b] = 1;
if (d[b][x]) d[a][x] = 1;
for (int y = 0; y < n; y ++ )
if (d[x][a] && d[b][y])
d[x][y] = 1;
}
type = check();
if (type) t = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");
else if (type == 2) printf("Inconsistency found after %d relations.\n", t);
else
{
memset(st, 0, sizeof st);
printf("Sorted sequence determined after %d relations: ", t);
for (int i = 0; i < n; i ++ ) printf("%c", get_min());
printf(".\n");
}
}
return 0;
}
344. 观光之旅
分析
按照环上的编号最大的点的编号来分类. 在floyd
的第一层循环下, 有d[i][j]数组: 从 i → j i \to j i→j, 只经过1 ~ k - 1的点的最短路径
可以发现, 可以用来求第k类
当前状态下, 所有的环都长这样, 因此可以枚举所有的点对i 和 j
当前固定i 和 j, i → k i \to k i→k, k → j k \to j k→j的长度固定, 因此想要让整个环, 长度最小, 必须让 j → i j \to i j→i最短, 但是这个距离正好是第一重循环到第k层的d[i][j]
因此, 第k类的最小值就是d[i][j] + w[i][k] + w[k][j]
求总的最小, 那么k从1 ~ n循环完后, 取min
就可以了
记录方案:
存路径的话, 只需要存在使得d[i][j] >= d[i][k] + d[k][j]
的k, pos[i][j] = k
, 然后递归处理[i, k], 和[k, j].
code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;
void get_path(int i, int j){
if (pos[i][j] == 0) return ;
int k = pos[i][j];
get_path(i, k);
path[cnt ++] = k;
get_path(k, j);
}
int main(){
cin >> n >> m;
memset(g, 0x3f, sizeof g);
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 d);
for (int k = 1; k <= n; k ++ ){
for (int i = 1; i < k; i ++ )
for (int j = i + 1; j < k; j ++ )
if ((long long)d[i][j] + g[j][k] + g[k][i] < res){
res = d[i][j] + g[j][k] + g[k][i];
cnt = 0;
path[cnt ++] = k;
path[cnt ++] = i;
get_path(i, j);
path[cnt ++] = j;
}
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];
pos[i][j] = k;
}
}
if (res == INF) puts("No solution.");
else {
for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
cout << endl;
}
return 0;
}
AcWing 345. 牛站
分析
因为两两状态之间是独立的, 因此具有结合律, 所以能用倍增的方法去求解