1. 问题描述:
给定一张 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
来源:https://www.acwing.com/problem/content/description/166/
2. 思路分析:
因为是有向无环图,所以可以使用dp来解决,dp其实是有向无环图上的最短路(dp其实与最短路有很大的联系,有向无环图可以保证没有后效性,这样当前依赖的状态之前就可以计算出来),这是一道属于比较简单的dp问题,我们可以使用f(i)来表示节点i能够到达的集合,其中f(i) = i | f(j1) | f(j2) ...,(自己也可以到自己)因为在计算f(i)的时候,所有的邻点f(j)都需要被先计算出来,所以我们需要先求解出拓扑排序,然后按照拓扑排序的逆序从后往前遍历计算f(i),这里还需要表示对应对应的集合,我们可以使用一个n位的二进制数字,1表示可达,0表示不可达,c++可以使用工具包中的bitset来表示一个n位的二进制数字,直接调用api会比较方便操作,而对于python语言好像没有类似位运算的工具包,可以使用左移运算和或运算来计算对应的结果,但是这样做会超时。
3. 代码如下:
c++代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <bitset>
using namespace std;
const int N = 30010, M = 30010;
int n, m;
int h[N], e[M], ne[M], idx;
int d[N], q[N];
bitset<N> f[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void topsort()
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if ( -- d[j] == 0)
q[ ++ tt] = j;
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
d[b] ++ ;
}
topsort();
for (int i = n - 1; i >= 0; i -- )
{
int j = q[i];
f[j][j] = 1;
for (int k = h[j]; ~k; k = ne[k])
f[j] |= f[e[k]];
}
for (int i = 1; i <= n; i ++ ) printf("%d\n", f[i].count());
return 0;
}
python好像没有类似于c++中bitset工具包,所以每一次都需要存储一个n位的二进制整数,所以时间复杂度为O(nm),肯定会超时:
import collections
from typing import List
class Solution:
# 拓扑排序
def topsort(self, n: int, d: List[int], g: List[List[int]], res: List[int]):
q = collections.deque()
for i in range(1, n + 1):
if d[i] == 0:
q.append(i)
res.append(i)
while q:
p = q.popleft()
for next in g[p]:
d[next] -= 1
if d[next] == 0:
q.append(next)
res.append(next)
def process(self):
n, m = map(int, input().split())
g = [list() for i in range(n + 10)]
d = [0] * (n + 10)
for i in range(m):
a, b = map(int, input().split())
g[a].append(b)
d[b] += 1
res = list()
self.topsort(n, d, g, res)
f = [0] * (n + 10)
for i in range(len(res) - 1, -1, -1):
# 对应位置的二进制数字置为1
f[res[i]] = 1 << res[i] - 1
for next in g[res[i]]:
# 或上邻接点的中可以到达的点的数目
f[res[i]] |= f[next]
for i in range(1, n + 1):
# 计算对应的二进制数字1的个数
print(bin(f[i]).count("1"))
if __name__ == '__main__':
Solution().process()