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()
返回是否有1foo.none()
返回是否没有1foo.set()
全都变成1foo.set(p)
将第p + 1位变成1foo.set(p, x)
将第p + 1位变成xfoo.reset()
全都变成0foo.reset(p)
将第p + 1位变成0foo.flip()
全都取反foo.flip(p)
将第p + 1位取反foo.to_ulong()
返回它转换为unsigned long的结果,如果超出范围则报错foo.to_ullong()
返回它转换为unsigned long long的结果,如果超出范围则报错foo.to_string()
返回它转换为string的结果