传递闭包
一、问题描述
二、问题简析
首先,要弄清楚传递闭包的定义,由题意:
一张图的邻接矩阵定义为一个 n × n n\times n n×n 的矩阵 A = ( a i j ) n × n A=(a_{ij})_{n\times n} A=(aij)n×n,其中
a i j = { 1 , i 到 j 存在直接连边 0 , i 到 j 没有直接连边 a_{ij}=\left\{ \begin{aligned} 1,i\ 到\ j\ 存在直接连边\\ 0,i\ 到\ j\ 没有直接连边 \\ \end{aligned} \right. aij={1,i 到 j 存在直接连边0,i 到 j 没有直接连边
一张图的传递闭包定义为一个 n × n n\times n n×n 的矩阵 B = ( b i j ) n × n B=(b_{ij})_{n\times n} B=(bij)n×n,其中
b i j = { 1 , i 可以直接或间接到达 j 0 , i 无法直接或间接到达 j b_{ij}=\left\{ \begin{aligned} 1,i\ 可以直接或间接到达\ j\\ 0,i\ 无法直接或间接到达\ j\\ \end{aligned} \right. bij={1,i 可以直接或间接到达 j0,i 无法直接或间接到达 j
如果我们把参数的含义改变一下,就可以很容易地发现本题其实是多源最短路径问题。
名称 | 改变前 | 改变后 |
---|---|---|
a i j = 1 a_{ij} = 1 aij=1 | i i i 到 j j j 有直接连边 | e ( i , j ) e(i, j) e(i,j) 存在且权值为0 |
a i j = 0 a_{ij} = 0 aij=0 | i i i 到 j j j 无直接连边 | e ( i , j ) e(i, j) e(i,j) 不存在 |
b i j = 1 b_{ij} = 1 bij=1 | i i i 可以直接或间接到达 j j j | i i i 到 j j j 存在最短路径 |
b i j = 0 b_{ij} = 0 bij=0 | i i i 无法直接或间接到达 j j j | i i i 到 j j j 不存在最短路径 |
因此,我们可以采取 F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall 解决。
三、本题代码
3.1 直接套用 F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall 模板
#include <bits/stdc++.h>
using namespace std;
#define MdX 103
#define INF 1e8
int n;
int d[MdX][MdX];
void solve(void)
{
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]);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
fill(begin(d[i]), end(d[i]), INF);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
int a;
scanf("%d", &a);
if (a != 0)
{
d[i][j] = a;
}
}
solve();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (d[i][j] == INF)
printf("%d ", 0);
else
printf("%d ", 1);
}
putchar('\n');
}
return 0;
}
注意:
- 1、初始化连接矩阵时要格外注意,与要来的算法不太一样。令
d [ i ] [ j ] = { e ( i , j ) . w , e ( i , j ) 存在且 i ≠ j I N F , e ( i , j ) 不存在或 i = = j d[i][j] = \begin{cases} e(i, j).w &, e(i, j)存在且 i \neq j \\ INF &,e(i, j)不存在或 i == j \end{cases} d[i][j]={e(i,j).wINF,e(i,j)存在且i=j,e(i,j)不存在或i==j
输入为 1,表示边存在;输入为 0,表示边不存在。需要注意自环是不存在的。 - 2、要输出得不是最短路径,而是传递闭包。令
p r i n t f = { 1 , d [ i ] [ j ] ≠ I N F 0 , d [ i ] [ j ] = = I N F \mathbf{printf} = \begin{cases} 1 &,d[i][j] \neq \mathbf{INF} \\ 0 &,d[i][j] == \mathbf{INF} \end{cases} printf={10,d[i][j]=INF,d[i][j]==INF
3.2 稍微改进一点
模板中, d [ i ] [ j ] = d[i][j] = d[i][j]= i i i 到 j j j 的最短路径。现在,令
d [ i ] [ j ] = { t r u e , i 可以到 j f a l s e , i 不可以到 j d[i][j] = \begin{cases} true &, i 可以到 j \\ false &, i 不可以到 j \end{cases} d[i][j]={truefalse,i可以到j,i不可以到j
F
l
o
y
d
−
W
a
r
s
h
a
l
l
Floyd-Warshall
Floyd−Warshall 的核心 d[i][j] = min(d[i][j], d[i][k] + d[k][j])
,变成 d[i][j] |= d[i][k] & d[k][j]
。可以这样理解:
i
i
i 能否到
j
j
j,有两种情况:1、
i
i
i 能否直接到
j
j
j;2、
i
i
i 能否由
k
k
k 中转到
j
j
j。有一种成立,
i
i
i 就能到
j
j
j。
#include <bits/stdc++.h>
using namespace std;
#define MAX 103
int n;
bool d[MAX][MAX];
void solve(void)
{
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
d[i][j] |= d[i][k] & d[k][j];
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &d[i][j]);
solve();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
printf("%d ", d[i][j]);
putchar('\n');
}
return 0;
}
完