首页 > 其他分享 >NC15707 可达性

NC15707 可达性

时间:2023-07-26 09:11:20浏览次数:48  
标签:10 连通 NC15707 入度 scc 点集 可达性 分量

NC15707 可达性

时间限制:\(C/C++\) \(1\)秒,其他语言\(2\)秒
空间限制:\(C/C++\) \(262144K\),其他语言\(524288K\)
\(64bit\) \(IO\) \(Format: \%lld\)

题目描述

给出一个 \(0 ≤ N ≤ 10^5\) 点数、\(0 ≤ M ≤ 10^5\) 边数的 有向图
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

输入描述:
第一行为两个整数 \(1 ≤ n, m ≤ 10^5\),
接下来 \(M\) 行,每行两个整数 \(1 ≤ u, v ≤ 10^5\) 表示从点 \(u\) 至点 \(v\) 有一条有向边。
数据保证没有重边、自环。

输出描述:
第一行输出一个整数 \(z\),表示作为答案的点集的大小;
第二行输出 \(z\) 个整数,升序排序,表示作为答案的点集。

示例\(1\)

输入

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

输出

2
4 7

思路

让求一个点集,使得从这些点出发能够到达任意一点,即是求能否找到一些强连通分量,这些强连通分量的入度为\(0\)。

如图,要想找到一个点集,使得从这些点出发能够到达任意一点,显然这个点集是\(5\)和\(6\)。\(5\)和\(6\)也都是一个独立的连通分量。

如图,若是\(5\)和\(6\)同属于一个强连通分量,根据题目要求,让尽可能小的点集和字典序最小的,那输出一个\(5\)就可以了。

因此,利用\(tarjan\)缩点,找出所有入度为\(0\)的强连通分量,找出里面最小一个的点。最后将所有的点再从小到大排序输出就行了。

\(Code\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N << 1;

int n, m;
int in[N];    // 入度数组
bool flag[N]; // 用于判断所在连通分量是否是目标连通分量,是则为1

// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// 有向图求强连通分量
int stk[N], top;    // tarjan算法需要用到的堆栈
bool in_stk[N];     // 是否在栈内
int dfn[N];         // dfs遍历到u的时间
int low[N];         // 从u开始走所能遍历到的最小时间戳
int ts;             // 时间戳,dfs序的标识,记录谁先谁后
int id[N], scc_cnt; // 强连通分量块的最新索引号
int sz[N];          // sz[i]表示编号为i的强连通分量中原来点的个数
// tarjan算法求强连通分量
void tarjan(int u) {
    dfn[u] = low[u] = ++ts;
    stk[++top] = u;
    in_stk[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in_stk[v])
            low[u] = min(low[u], dfn[v]);
    }

    if (dfn[u] == low[u]) {
        ++scc_cnt; // 强连通分量的序号
        int x;     // 临时变量x,用于枚举栈中当前强连通分量中每个节点
        do {
            x = stk[top--];    // 弹出节点
            in_stk[x] = false; // 标识不在栈中了
            id[x] = scc_cnt;   // 记录每个节点在哪个强连通分量中
            sz[scc_cnt]++;     // 这个强连通分量中节点的个数+1
        } while (x != u);
    }
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d %d", &n, &m);

    while (m--) {
        int a, b;
        scanf("%d %d", &a, &b);
        add(a, b); // 有向图
    }
    // Tarjan算法套路
    for (int i = 1; i <= n; i++) // 疑问:如果有多个彼此不连通的连通块,那是不是不存在可以到达所有点的点集?
        if (!dfn[i]) tarjan(i);

    // 枚举所有出边
    for (int u = 1; u <= n; u++)
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            int a = id[u], b = id[v]; // u和v不属于同一个强连通分量,且是u ->v
            if (a != b) in[b]++;      // 则v所在的强连通分量的入度+1
        }

    // 遍历每个强连通分量,找出入度为0的强连通分量
    for (int i = 1; i <= scc_cnt; i++)
        if (!in[i]) flag[i] = 1;

    // 遍历每个节点,如果它所在有强连通分量是入度为零的,则把此点记录到答案数组中,
    // 并且,修改此强连通分量的入度不是0,防止再次被加入到答案数组中
    set<int> ans;
    for (int i = 1; i <= n; i++) {
        int x = id[i];
        if (flag[x]) {
            ans.insert(i);
            flag[x] = 0;
        }
    }
    cout << ans.size() << endl;
    for (auto x : ans) cout << x << " ";
    return 0;
}

总结

  • ① \(Tarjan\)求出强连通分量后,再利用\(1 \sim n\)枚举所有边,统计一下每条边的两个端点是否在同一个\(scc\)中,统计每个\(scc\)的入度
  • ② 利用\(1 \sim scc_cnt\)的循环,对于入度为零的\(scc\)进行标识
  • ③ 从\(1 \sim n\)由小到大枚举每个点,发现所在\(scc\)的 入度为零,则将该点记录到答案中,并且设置\(flag[id]=0\),这个非常妙,可以有效防止后续该\(scc\)中其它大的序号点入答案
  • ④ 用\(set\)可以避免用数组后再排序

标签:10,连通,NC15707,入度,scc,点集,可达性,分量
From: https://www.cnblogs.com/littlehb/p/17581532.html

相关文章

  • 164. 可达性统计
    题目描述给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量f1-拓扑排序+状态压缩基本分析怎么梳理出统计的顺序?拓扑排序怎么统计?按照拓扑序的逆序记录可达性N在30000规模,怎么维护可达性?利用bitset进行状态压缩代码#include<iostream>#includ......
  • 图结构练习——BFSDFS——判断可达性
    图结构练习——BFSDFS——判断可达性TimeLimit:1000MSMemorylimit:65536K题目描述 输入第一行包含两个整数n,m(分别代表n个隘口,这些隘口之间有m个通道)。下面m行每行包含两个整数a,b;表示从a出发有一条通道到达b隘口(注意:通道是单向的)。输出......
  • AcWing 可达性统计(bitset
    可达性统计建图图的存储拓扑排序:DAG(有向无环图),往拓扑排序思考。拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。此类问题需要使用bitset优化。bitset在bitset头文件中,它类似数组,并且每一个元素只能是0或1,每个元素只用1bit空间,可看作几个in......
  • JVM:并发的可达性分析
    当前主流编程语言的垃圾收集器基本上都是依靠可达性分析算法来判定对象是否存活的,可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,这意味着必须全程冻结用户线程的运行。在根节点枚举这个步骤中,由于GCRoots相比起整个Java堆中全部的对象毕竟......
  • Java - 简单可达性分析
    可以作为GCRoot的对象1.方法区中常量引用的对象2.方法区中静态属性引用的对象3.虚拟机栈中引用的对象4.本地方法栈中引用的对象可达性分析通过GC......
  • ACWing 可达性统计
    ACWing可达性统计bitset可以说是一个多位二进制数,每八位占用一个字节,因为支持基本的位运算,所以可用于状态压缩,n位bitset执行一次位运算的时间复杂度可视为n/32.bitset<......