B3611 【模板】传递闭包

这篇博客讨论了如何求解有向图的传递闭包问题,提供了Floyd算法以及两种优化策略:bitset优化和位压优化,分别针对不同数据范围的效率提升。文章强调了在数据范围扩大到n≤2000时,优化算法的重要性,并指出bitset优化为当前最优解。

题目描述

给定一张点数为 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)。

最优解AC记录

#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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值