题目描述
给定一张点数为 nn 的有向图的邻接矩阵,图中不包含自环,求该有向图的传递闭包。
一张图的邻接矩阵定义为一个 n\times nn×n 的矩阵 A=(a_{ij})_{n\times n}A=(aij)n×n,其中
a_{ij}=\left\{ \begin{aligned} 1,i\ 到\ j\ 存在直接连边\\ 0,i\ 到\ j\ 没有直接连边 \\ \end{aligned} \right.aij={1,i 到 j 存在直接连边0,i 到 j 没有直接连边
一张图的传递闭包定义为一个 n\times nn×n 的矩阵 B=(b_{ij})_{n\times n}B=(bij)n×n,其中
b_{ij}=\left\{ \begin{aligned} 1,i\ 可以直接或间接到达\ j\\ 0,i\ 无法直接或间接到达\ j\\ \end{aligned} \right.bij={1,i 可以直接或间接到达 j0,i 无法直接或间接到达 j
输入格式
输入数据共 n+1n+1 行。
第一行一个正整数 nn。
第 22 到 n+1n+1 行每行 nn 个整数,第 i+1i+1 行第 jj 列的整数为 a_{ij}aij。
输出格式
输出数据共 nn 行。
第 11 到 nn 行每行 nn 个整数,第 ii 行第 jj 列的整数为 b_{ij}bij。
输入输出样例
输入 #1复制
4 0 0 0 1 1 0 0 0 0 0 0 1 0 1 0 0
输出 #1复制
1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1
说明/提示
对于 100\%100% 的数据,1\le n\le 1001≤n≤100,保证 a_{ij}\in\{0,1\}aij∈{0,1} 且 a_{ii}=0aii=0。
本题小故事:这道题本来在主题库,由于过于简单,经过 LA 群激烈的讨论,被某位管理员迁移到了入门与面试。
传递闭包,没有什么可以过多介绍的,就是传统 Floyd 的想法。
复杂度:O(n^3)O(n3)
#include <bits/stdc++.h>
using namespace std;
int n;
int a[110][110];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
for (int k = 1; k <= n; k++)//记得k循环在i和j之前
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
a[i][j] |= a[i][k] & a[k][j];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
cout << a[i][j] << ' ';
cout << endl;
}
return 0;
}
虽然 Floyd 能通过这道题,但 Floyd 是 O(n^3)O(n3) 的算法,太慢了。
思考:如果把这道题的数据范围换成 n\le2000n≤2000 该怎么办呢?
以下提供两种优化思路。
第一种为 bitset 优化,相对来说跑的要快一些。
这种方法也很好写,目前是此题的最优解(21ms)。
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
{
f = ch != '-';
ch = getchar();
}
while (isdigit(ch))
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return f ? x : -x;
}
int n;
bitset<110> a[110];
int main()
{
n = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
a[i][j] = read();
for (int j = 1; j <= n; j++)//注意j循环在i循环外
for (int i = 1; i <= n; i++)
if (a[i][j])
a[i] |= a[j];//bitset也挺好写的
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
putchar(a[i][j] + '0'), putchar(' ');
putchar('\n');
}
return 0;
}
第二种为压位优化,就是把连续 3232 个点的状态压缩成一个 int 类型的数,类似于状压dp。这样既节省了空间,也节约了时间。
代码只要理解了,还是挺好写的。虽然 bitset 肯定最好写的,但如果不想用 STL 的话,压位肯定是最好的,只是要注意的细节很多。
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int n,m,a[N][N];
char c;
int main ()
{
scanf("%d",&n);
m=n/32+1;
for (int i=0;i<n;i++)
for (int j=0;j<m;j++)
for (int k=0;k<32&&j*32+k<n;k++)
{
char c=getchar();
while (c!='0'&&c!='1')
c=getchar();
a[i][j]|=(c-'0')<<k;
}
for (int k=0;k<n;k++)
for (int i=0;i<n;i++)
{
int f=-((a[i][k/32]>>(k%32))&1);
if (f==0)
continue;
for (int j=0;j<m;j++)
a[i][j]=a[i][j]|(f&a[k][j]);
}
for (int i=0;i<n;i++)
{
for (int j=0;j<m;j++)
for (int k=0;k<32&&j*32+k<n;k++)
putchar(((a[i][j]>>k)&1)+'0'),putchar(' ');
putchar('\n');
}
return 0;
}
这篇博客讨论了如何求解有向图的传递闭包问题,提供了Floyd算法以及两种优化策略:bitset优化和位压优化,分别针对不同数据范围的效率提升。文章强调了在数据范围扩大到n≤2000时,优化算法的重要性,并指出bitset优化为当前最优解。
3万+

被折叠的 条评论
为什么被折叠?



