Floyd传递闭包-实用小知识

闭包是什么呢?

事实上牠可以被理解为一个关系网络中的关系,你认为牠是一个图上的边

我们要解决的问题就是,利用已知条件,想办法把点之间的关系尽可能多地确定出来

先举个例题作为例子

Luogu P2419 [USACO08JAN]Cow Contest S

题意概括:我们有 n 头牛,知道 m 个二元组关系( x , y ),代表 x 号牛的OI比 y 号牛强,需要我们输出我们最多可以确定几头牛的排名

显然,只有确定与其牠所有牛的关系,我们才能确定第 i 号牛的排名

为了做到这一点,我们只能通过点和点之间的关系进行转移,会想到一个熟悉的算法,Floyd

原来 \(d[x][y]\)代表的是点\(x\)\(y\)之间的最短路长度,现在我们把牠改为\(x\)\(y\)之间的关系是否确定

\(若确定,则d[x][y]=1,否则d[x][y]=0\)

参照Floyd原来的代码,写出以下程序

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];

这一波就把所有可以确定的关系确定了

然后我们来看这题,我们重新设计一下d

\(若x比y强,d[x][y]=1 ; 否则,d[x][y]=0\)

然后就是和上边一样的处理关系

详见代码

#include <bits/stdc++.h>
using namespace std;

const int N=110;
bool g[N][N];
int n,m;

void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j] |= g[i][k] & g[k][j];
}

int main()
{
    cin>>n>>m;

    while(m--)
    {
        int a,b;
        cin>>a>>b;
        g[a][b]=true;
    }

    floyd();

    int res=0;
    for(int i=1;i<=n;i++)
    {
        int cnt=0;//代表已经确定的关系数量
        for(int j=1;j<=n;j++)
            if(g[i][j] || g[j][i])
                cnt++;
        if(cnt == n-1) res++;//和所有节点都确定了就可以确定牠的排名
    }
    cout<<res<<endl;
    return 0;
}

板子题做完了,来一道高级经验题

Luogu P4306 [JSOI2010]连通数

题意:给你一个有向图,求出每个节点能到达的节点数之和

显然这道题也是求闭包,但是数据是\(N<=2000\),显然\(O(n^3)\)的算法是无法跑过的

但是结合求闭包需要用到许多位运算,我们就想到了bitset
(不知道的自行百度吧)

由于bitset自带一个1/32的常数优化,使我们优雅而轻松地水掉了这题

#include<bits/stdc++.h>
using namespace std;
const int N=2010;
bitset<N> d[N];
string a;
int n,m,ans;
int main()
{
	 cin>>n;
	 for(int i=1;i<=n;i++)
	 {
		 cin>>a;
		 for(int j=1;j<=n;j++)
			 if(a[j-1]=='1')
				 d[i][j]=1;
			 d[i][i]=1;
	 }
	 for(int k=1;k<=n;k++)
	      for(int i=1;i<=n;i++)
		 if(d[i][k])
		 	d[i]|=d[k];
	 for(int i=1;i<=n;i++)
	 	ans+=d[i].count();
	 printf("%d",ans);
	 return 0;
}
posted @ 2022-07-17 17:10  羊扬羊  阅读(104)  评论(0编辑  收藏  举报