题目描述
给定一张 NN 个点 MM 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。
输入格式
第一行两个整数 N,MN,M,接下来 MM 行每行两个整数 x,yx,y,表示从 xx 到 yy 的一条有向边。
输出格式
输出共 NN 行,表示每个点能够到达的点的数量。
数据范围
1≤N,M≤300001≤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
拓扑排序+位运算
分析
使用了bitset
-
首先进行一次topo排序,记录拓扑排序的顺序
-
然后倒序遍历拓扑排序的顺序,对于逆序中每个点
-
首先该点可以到达它自己
-
然后记录该点能够到达其他的所有点(用bitset 加 或运算表示)
- 遍历该点能到的所有点(邻接表),用bitset 加 或运算表示该点能到的所有点
-
-
然后遍历一遍所有点,看每个点能够到达点的数量(bitset中1的个数)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<bitset>
using namespace std;
const int N = 30010;
const int M = N * 2;
int h[N], e[M], ne[M], idx = 0;
int d[N]; // 入度
int seq[N], cnt = 0; // 每个点到其他点的距离
bitset<N> f[N]; // 前面的N表示每个bitset的长度
// 后面的N表示有N个bitset
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int n, m;
void topo()
{
queue<int> q;
for(int i = 1; i <= n; i++)
if(!d[i])
{
seq[cnt++] = i;
q.push(i);
}
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(!d[j]) continue;
d[j]--;
if(!d[j])
{
seq[cnt++] = j;
q.push(j);
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof(h));
while(m --)
{
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
d[y]++; // 入度++
}
topo();
// 倒序遍历topo排序
for(int i = n-1; i >= 0; i--)
{
int j = seq[i]; // j: 1 -> n中某个数
f[j][j] = 1; // j点的第j位为1
for(int k = h[j]; k != -1; k = ne[k])
{
f[j] |= f[e[k]]; // j点能到的所有点包括其指向的点能到的点的并集
}
}
for(int i = 1; i <= n; i++) printf("%d\n", f[i].count());
return 0;
}