164 可达性统计(拓扑排序求解dp问题)

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()

おすすめ

転載: blog.csdn.net/qq_39445165/article/details/121584184