【HNU】数据结构-实验-图-多路径

这是HNU数据结构课程的实验六,实际上来源于Codeforces 1547G,还是有一定难度的,让我们来看看这次的题目吧!

原题链接:1547G How Many Paths?

题目

【问题描述】

给定一个有向图 G G G,可以包含环,也可以有自环(节点连接到自身),一对顶点之间最多有一条边相连。顶点编号从 1 ∼ n 1 \sim n 1n
从顶点 u u u v v v 的路径是一个边的序列,描述如下:

  • 顶点 u u u 是路径上第一条边的开始顶点
  • 顶点 v v v 是路径上最后一条边的结束顶点
  • 对于所有相邻边对,后一条边的开始顶点是前一条边的结束顶点

假定边的空序列是从 u u u u u u 的路径。

对于每个顶点 v v v,输出以下四个值之一:

  • 0 0 0,表示从顶点 1 1 1 v v v 没有路径
  • 1 1 1,表示从顶点 1 1 1 v v v 只有一条路径
  • 2 2 2,表示从顶点 1 1 1 v v v 有超过一条路径,且路径数是有限的
  • − 1 -1 1,表示从顶点 1 1 1 v v v 有无穷多条路径

示例如下:

示例

那么,

  • 顶点 1 1 1 的结果为 1 1 1:从 1 1 1 1 1 1 只有一条路径(路径长度为 0 0 0
  • 顶点 2 2 2 的结果为 0 0 0:从 1 1 1 2 2 2 没有路径
  • 顶点 3 3 3 的结果为 1 1 1:从 1 1 1 3 3 3 仅有一条路径(为边 ( 1 , 3 ) (1, 3) (1,3)
  • 顶点 4 4 4 的结果为 2 2 2:从 1 1 1 4 4 4 有超过一条路径,但路径数量是有限的(两条路径: [ ( 1 , 3 ) , ( 3 , 4 ) ] [(1, 3), (3, 4)] [(1,3),(3,4)] [ ( 1 , 4 ) ] [(1, 4)] [(1,4)]
  • 顶点 5 5 5 的结果为 − 1 -1 1:从 1 1 1 5 5 5 的路径数是无穷的(环可以用于路径无穷多次)
  • 顶点 6 6 6 的结果为 − 1 -1 1:从 1 1 1 6 6 6 的路径数是无穷的(环可以用于路径无穷多次)

【输入形式】

输入的第一行为一个整数 t   ( 1 ≤ t ≤ 10 4 ) t \ (1 \leq t \leq 10^4) t (1t104),表示测试用例的组数,接下来是 t t t 个测试用例,每个测试用例前有一个空行,这个空行的作用仅为阅读方便,有否不影响最终结果。

每个测试用例的第一行为两个整数 n n n m   ( 1 ≤ n ≤ 4 × 10 5 , 1 ≤ m ≤ 4 × 10 5 ) m \ (1 \leq n \leq 4 \times 10^5, 1 \leq m \leq 4 \times 10^5) m (1n4×105,1m4×105),分别表示图的顶点数和边数。接下来的 m m m 行包括了对边的描述,每行两个整数 a i a_i ai b i   ( 1 ≤ a i , b i ≤ n ) b_i \ (1 \leq a_i, b_i \leq n) bi (1ai,bin),表示第 i i i 边的开始点和结束点,图的顶点编号从 1 1 1 n n n ,给定的图中可以包含环(可能有 a i = b i a_i = b_i ai=bi), 但不可以包含多边(即对于 i ≠ j i \neq j i=j,不可能有 a i = a j a_i = a_j ai=aj 以及 b i = b j b_i = b_j bi=bj)。

所有测试用例中 n n n 之和不超过 4 × 10 4 4 \times 10^4 4×104,同样的,所有的 m m m 之和不超过 4 × 10 4 4 \times 10^4 4×104

【输出形式】

输出包括 t t t 行,第 i i i 行是第 i i i 个测试用例的答案,为一个 n n n 个整数序列,取值在 − 1 -1 1 2 2 2 之间。

【样例输入】

5

6 7
1 4
1 3
3 4
4 5
2 1
5 5
5 6

1 0

3 3
1 2
2 3
3 1

5 0

4 4
1 2
2 3
1 4
4 3

【样例输出】

1 0 1 2 -1 -1 
1 
-1 -1 -1 
1 0 0 0 0 
1 1 2 1

题目分析

看到这道题目首先容易想到的是强联通缩点+拓扑排序。

考虑从 1 1 1 号顶点到某个顶点路径数为无穷的情况。若一个点处于顶点数大于 1 1 1 S C C \mathrm{SCC} SCC 中,并且顶点 1 1 1 可到达此 S C C \mathrm{SCC} SCC,则到该点的路径数为无穷。因为来到此 S C C \mathrm{SCC} SCC 后,其中的任意顶点互相可达,可以不断绕圈。另外,若一个点存在自旋边,则从顶点 1 1 1 到该点的路径数显然也为无穷。

所以先使用 T a r j a n \mathrm{Tarjan} Tarjan 进行缩点,得到一个 D A G \mathrm{DAG} DAG,每个 S C C \mathrm{SCC} SCC 内的点路径数显然是相同的。此时 S C C \mathrm{SCC} SCC 序号恰为新 D A G \mathrm{DAG} DAG 的拓扑序逆序,直接在新图上进行一次 D P \mathrm{DP} DP 即可。

另外此题还有染色+多次 D F S \mathrm{DFS} DFS官方解法,个人认为比较难想到。

代码

#include <bits/stdc++.h>
using namespace std;

template <typename T>
using vec2 = vector<vector<T>>;

vec2<int> G;
vector<int> low, num, scc_id, scc_size;
stack<int> dfs_s; //dfs栈
int dfn; //dfs序
int cnt; //强联通分量个数

void dfs(int u) {
    dfs_s.push(u);
    low[u] = num[u] = ++dfn;
    for (auto v : G[u]) {
        if (!num[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
        } else {
            //处理回退边
            if (!scc_id[v]) {
                low[u] = min(low[u], num[v]);
            }
        }
    }
    if (low[u] == num[u]) {
        //u是scc祖先
        cnt++;
        int v;
        scc_size.push_back(0);
        do {
            v = dfs_s.top();
            dfs_s.pop();
            scc_id[v] = cnt;
            scc_size[cnt]++;
        } while (u != v);
    }
}

void Tarjan(int n) {
    cnt = dfn = 0;
    low = vector<int>(n + 1);
    num = vector<int>(n + 1);
    scc_id = vector<int>(n + 1);
    scc_size = vector<int>{0};

    for (int i = 1; i <= n; i++) {
        if (!num[i]) {
            dfs(i);
        }
    }
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int t;
    cin >> t;
    while (t--) {
        int n, m;
        cin >> n >> m;

        G = vec2<int>(n + 1, vector<int>());

        for (int i = 0; i < m; i++) {
            int u, v;
            cin >> u >> v;
            G[u].push_back(v);
        }

        Tarjan(n);

        vec2<int> G2(cnt + 1, vector<int>()); //scc缩点后的图
        vector<bool> self_loop(n + 1); //scc是否是单点自环
        for (int i = 1; i <= n; i++) {
            for (auto j : G[i]) {
                int u = scc_id[i], v = scc_id[j];
                if (u != v) {
                    G2[u].push_back(v);
                } else if (i == j) {
                    self_loop[u] = 1;
                }
            }
        }

        vector<int> dp(cnt + 1);
        int s = scc_id[1];
        //边界条件
        if (scc_size[s] > 1 || self_loop[s]) {
            dp[s] = -1;
        } else {
            dp[s] = 1;
        }

        //scc编号为逆拓扑序
        for (int u = cnt; u >= 1; u--) {
            //不可达的点对后续没有贡献
            if (dp[u] == 0) continue;
            for (auto v : G2[u]) {
                //状态转移
                if (dp[u] == -1 || scc_size[v] > 1 || self_loop[v]) {
                    dp[v] = -1;
                } else if (dp[v] != -1) {
                    dp[v] = min(dp[v] + dp[u], 2);
                }
            }
        }

        for (int i = 1; i <= n; i++) {
            cout << dp[scc_id[i]] << " \n"[i == n];
        }
    }


    return 0;
}

如果觉得有帮助的话就点个赞吧~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值