可达性统计 拓扑排序,bitset,状态压缩

AcWing 164. 可达性统计

给定一张 N 个点 M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。

输入格式

第一行两个整数 N,M,接下来 M 行每行两个整数 x,y,表示从 x 到 y 的一条有向边。

输出格式

输出共 N 行,表示每个点能够到达的点的数量。

数据范围

1≤N,M≤30000

输入样例:

10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9

输出样例:

1
6
3
3
2
1
1
1
1
1

思路:

要统计从每个点出发能到达的点数,由拓扑排序特点知,
对于边(x,y),x都出现在y之前。所以从x出发能到达的点f(x)为自身再加上从x的各个后继节点y出发能够到达的点的并集。因为是有向无环图,所以明显是拓扑排序。我们先进行拓扑排序,再逆序进行计算。

那么如何求并集呢?
采用状态压缩的方法,用二进制数来存储每个f(x),第i位为1表示x能到达i,0表示不能到达。
又因为按位或运算 :有1为1,所以求这些集合的并集等价于对若干个二进制数做按位或运算。
最后每个f(x)中1的个数就是从x出发能够到达的点的个数.

我们可以采用bitset来存储f(x),
因为它有一个计算二进制表示下有多少个1的函数,f().count(),非常方便快捷! 

代码如下:


#include <bits/stdc++.h>
#include <bitset>
using namespace std;
#define N 60010
int n,m,head[N],ne[N],ver[N],deg[N],tot,cnt,a[N];//a为空的拓扑序列 
bitset<N> f[N];//bitset! 超有用!!! bitset在编译时就需要确定的位数,这里设置为<maxn>
void add(int x, int y){//在邻接表中添加一条有向边 
	ver[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
	deg[y]++;//y的入度加1 
}
void topsort(){
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(deg[i]==0) q.push(i);//将入度为0的点加入队列 
	} 
	while(q.size()){
		int x=q.front();
		q.pop();
		a[++cnt]=x;//加入拓扑序列 
		for(int i=head[x];i;i=ne[i]){
			int y=ver[i];//x的各个后继节点y 
			if(--deg[y]==0) q.push(y);//从x已经到y, 把y的入度减1,如果此时入度减为0,把y加入队列 
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		add(x,y);
	}
	topsort();//拓扑排序入口 
	for(int i=cnt;i>=1;i--){//逆序计算 
	    int x=a[i];
	    f[x][x]=1;//自身能够到达自身
	    for(int j=head[x];j;j=ne[j]){
	    	int y=ver[j];
	    	f[x]|=f[y]; //状态压缩,进行若干次或运算 
		}
	} 
	for(int i=1;i<=n;i++){
		cout<<f[i].count()<<endl;//输出每个点能到达的点的个数 
	}
	return 0;
}

bitset: 

 https://www.cnblogs.com/magisk/p/8809922.html

常用函数:

对于一个叫做foo的bitset:
foo.size() 返回大小(位数)
foo.count() 返回1的个数
foo.any() 返回是否有1
foo.none() 返回是否没有1
foo.set() 全都变成1
foo.set(p) 将第p + 1位变成1
foo.set(p, x) 将第p + 1位变成x
foo.reset() 全都变成0
foo.reset(p) 将第p + 1位变成0
foo.flip() 全都取反
foo.flip(p) 将第p + 1位取反
foo.to_ulong() 返回它转换为unsigned long的结果,如果超出范围则报错
foo.to_ullong() 返回它转换为unsigned long long的结果,如果超出范围则报错
foo.to_string() 返回它转换为string的结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值