图论:tarjan 算法求解强连通分量

题目描述

有一个 n n n 个点, m m m 条边的有向图,请求出这个图点数大于 1 1 1 的强连通分量个数。

输入格式

第一行为两个整数 n n n m m m

第二行至 m + 1 m+1 m+1 行,每一行有两个整数 a a a b b b,表示有一条从 a a a b b b 的有向边。

输出格式

仅一行,表示点数大于 1 1 1 的强连通分量个数。

5 4
2 4
3 5
1 2
4 1
1

提示

数据规模与约定

对于全部的测试点,保证 2 ≤ n ≤ 1 0 4 2\le n \le 10^4 2n104 2 ≤ m ≤ 5 × 1 0 4 2\le m\le 5\times 10^4 2m5×104 1 ≤ a , b ≤ n 1 \leq a, b \leq n 1a,bn


基本概念梳理

强连通图

在一个有向图中,任意两个节点之间都能相互到达,那么这个图就是一个强连通图。

强连通分量(Strongly Connected Components, SCC)

强连通图是非常少的,但是在有向图中,如果存在某些节点子集 A A A,任意两个节点可以相互到达,且如果往子集 A A A 中再加入新的节点,那么子集中任意两个节点之间无法相互到达,那么称这些节点为强连通分量。如下图,共有三个强连通分量,红框中的节点 1 , 2 , 3 1,2,3 1,2,3 组成了一个强连通分量,绿框中的节点 4 , 5 4,5 4,5 组成了另一个强连通分量,蓝框中的节点 6 6 6 是一个强连通分量。
在这里插入图片描述

D F S DFS DFS 遍历

之前我们学习 D F S DFS DFS 遍历的时候,有两种常用的遍历方式。

方式 1 1 1:先访问当前节点,再递归相邻节点。(类似求树的深度)

方式 2 2 2:先递归相邻节点,再访问当前节点。(类似求子树大小)

t a r j a n tarjan tarjan 算法思路

对于 t a r j a n tarjan tarjan 算法,我们用的 d f s dfs dfs 遍历 是上面 方式 2 2 2

对于每个节点来说,有两个重要属性,我通常定义为 i d id id t x tx tx,其中 i d id id 表示 首次到达节点的时间戳, t x tx tx 表示从当前节点出发,可以遍历到的 i d id id 最小的节点的值,对于上面的图,我们可以从节点 1 1 1 出发进行一次【方式 2 2 2】 的 d f s dfs dfs 遍历,试试 可以得到每个节点的 i d id id 值和 t x tx tx 值分别是多少?如下:

节点 1 : i d = 1 , t x = 1 1: id = 1, tx = 1 1:id=1,tx=1

节点 2 : i d = 2 , t x = 1 2:id = 2, tx = 1 2:id=2,tx=1

节点 3 : i d = 3 , t x = 1 3:id = 3, tx = 1 3:id=3,tx=1

节点 4 : i d = 4 , t x = 4 4: id = 4, tx = 4 4:id=4,tx=4

节点 5 : i d = 5 , t x = 4 5: id = 5, tx = 4 5:id=5,tx=4

节点 6 : i d = 6 , t x = 6 6: id = 6, tx = 6 6:id=6,tx=6

通过观察可以发现,同一个强连通分量中的节点,它们的 t x tx tx 值是相等的,而且,强连通分量中的起点的 i d id id 一定等于 t x tx tx

在整个 d f s dfs dfs 搜索过程中,我们需要借助 栈 来保存遍历到的元素,到达某个节点后,先从这个节点开始搜,搜索结束后,更新当前节点的 t x tx tx 值,所有邻接点全部搜索完后,判断当前节点是否是当前这个强连通分量的起点(即 i d id id 是否等于 t x tx tx),如果是,从栈空间中取出当前强连通分量的所有节点即可。

比如,找第一个强连通分量,栈结构如下:
在这里插入图片描述

此时,对于节点 1 1 1 来说,在栈中的位置到栈顶,所有元素都是以 节点 1 1 1 为起点的强连通分量中的元素。

其他两个强连通分量类似,在此不画,可以通过代码来理解。

代码

#include "bits/stdc++.h"
using namespace std;
const int N = 2e4+7, M = 5e4+7;
struct Edge{
    int v, ne;
}es[M];
struct Node{
    int id, tx;
}f[N];
int n, t=1, m, u, v, idx = 1, h[N], ans;
stack<int> stk;
bool vis[N];  // 记录节点是否在栈中,在栈中标记为true,否则为false
// dfs函数,传入节点编号,开始从当前节点进行 先搜索后访问的 dfs
void dfs(int u) {
    f[u].id = t;  // 首次到达的时间戳
    f[u].tx = t++;  // 可以回溯到最小的时间戳,初始的时候就是 t,跟id 一样
    stk.push(u);   // 当前节点进栈
    vis[u] = true;  // 所有进栈的元素全部标记
    for(int e = h[u]; e; e = es[e].ne) {  // 链式前向星遍历节点 u 的所有邻接点
        int v = es[e].v;  // u 的 邻接点 v
        if(f[v].id == 0) {   // 节点v 还没有访问过
            dfs(v);  // 先搜索
            f[u].tx = min(f[u].tx, f[v].tx);  // 再更新可以回溯到的最小时间戳
        } else if(vis[v]) {  // 节点v访问过,如果在栈中,那就一定是当前强连通分量中的节点
            f[u].tx = min(f[u].tx, f[v].tx);  // 更新最小时间戳
        }
    }
    if(f[u].id == f[u].tx) {  // 节点u 是当前强连通分量中的起点,也可以成为当前强连通分量的根节点
        int cnt = 0; // 本次强连通分量的节点个数,计算的是大于1 的强连通分量的个数
        while(!stk.empty() && stk.top() != u) {  // 栈顶这些元素都是和 u 一个强连通分量的,而且 u 是这个强连通分量的鼻祖
            vis[stk.top()] = false;  // 出栈更新vis
            stk.pop();  // 出栈
            cnt++;  // 强连通分量中节点数+1
        }
        vis[stk.top()] = false;  // 本次是 u 节点
        stk.pop();  // u 出栈
        cnt++;  // 个数+1
        if(cnt > 1) ans++;  // 统计点数大于 1 的强连通分量的个数
    }
}
// 链式前向星建图
void add(int u, int v) {
    es[idx] = {v, h[u]};
    h[u] = idx++;
    return;
}
int main() {
    cin >> n >> m;
    while(m--) {
        cin >> u >> v;
        add(u, v);   // 有向图
    }
    for(int u=1; u<=n; u++) {
        if(f[u].id == 0) dfs(u);  // 当前节点还没有遍历过,开始从当前节点搜
    }
    cout << ans << endl;
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/973121.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UE5.3 C++ TArray系列(一)

一.TArray概述 它们就相当于C动态数组Vector&#xff0c;但是被UE封装了&#xff0c;懂得都懂反射嘛&#xff0c;要不一不小心就被回收了。 它真的非常常见&#xff0c;我所用的容器中&#xff0c;它绝对排名第一&#xff0c;第二是TMap。 同类好理解&#xff0c;我平时也常用…

【微中子代理踩坑-前端node-sass安装失败】

微中子代理踩坑-前端node-sass安装失败-windows 1.npm版本2.python2.73.安装Visual Studio 1.npm版本 当前使用node版本13.12.0 2.python2.7 安装python2.7.9并配置环境变量 3.安装Visual Studio 安装Visual Studio 我是直接勾选了3个windows的sdk,然后就好了 最后 npm in…

一文了解PLM项目管理系统

PLM项目管理系统详解 PLM项目管理系统的定义和功能 PLM&#xff08;Product Lifecycle Management&#xff0c;产品生命周期管理&#xff09;项目管理系统是一种全面管理产品从概念设计、开发、制造、服务到最终退役全过程的软件系统。该系统通过集成产品数据、工作流程、人员…

Pycharm+CodeGPT+Ollama+Deepseek

首先&#xff0c;体验截图&#xff1a; 接着&#xff1a; 1、下载Ollama&#xff1a; Download Ollama on macOS 2、下载模型 以1.5b为例&#xff0c;打开命令行&#xff0c;输入: ollama run deepseek-r1:1.5b 3、Pycharm安装Code GPT插件 打开PyCharm&#xff0c;找到文…

Netty入门详解

引言 Netty 是一个基于 Java 的高性能、异步事件驱动的网络应用框架&#xff0c;用于快速开发可维护的高性能网络服务器和客户端。它提供了一组丰富的 API&#xff0c;使得开发人员能够轻松地处理各种网络协议&#xff0c;如 TCP、UDP 等&#xff0c;并且支持多种编解码方式&a…

【C++】优先级队列宝藏岛

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

【分布式理论11】分布式协同之分布式事务(一个应用操作多个资源):从刚性事务到柔性事务的演进

文章目录 一. 什么是分布式事务&#xff1f;二. 分布式事务的挑战三. 事务的ACID特性四. CAP理论与BASE理论1. CAP理论1.1. 三大特性1.2. 三者不能兼得 2. BASE理论 五. 分布式事务解决方案1. 两阶段提交&#xff08;2PC&#xff09;2. TCC&#xff08;Try-Confirm-Cancel&…

Linux-C/C++《C++/1、C++基础》(C++语言特性、面向对象等)

这里主要介绍概念为主&#xff0c;主要介绍 C与 C 语言中常用的不同点&#xff0c;和一些新的变化。其中不会去说指针、数据类型、变量类型、判断和循环等这些知识&#xff0c;这些和C 语言基本是一样使用的。我们主要学习 C的面向对象编程&#xff0c;对学习 Qt 有很大的帮助。…

我国首条大型无人机城际低空物流航线成功首航

首航震撼开场&#xff1a;羊肉 “飞” 越 540 公里 在夜色的笼罩下&#xff0c;榆阳马合通用机场的跑道上&#xff0c;一架大型固定翼无人机蓄势待发&#xff0c;机身被灯光照亮&#xff0c;宛如一只即将展翅翱翔的钢铁巨鸟。它的货舱里&#xff0c;满满装载着新鲜的榆林羊肉&a…

python-leetcode 38.翻转二叉树

题目&#xff1a; 给定一个二叉树的根节点root,检查它是否轴对称。 方法一&#xff1a;递归 如果一个树的左子树与右子树镜像对称&#xff0c;那么这个树是对称的。 互为镜像的条件&#xff1a;他们的两个根结点具有相同的值&#xff0c;每棵树的右子树都与另一个树的左子树…

锐浪报表 Grid++Report 通用软件,通用模板个性打印

为提高软件打印报表的适应性&#xff0c;再软件中&#xff0c;使用统一的模板&#xff0c;实现个性化的调整。实现不同用的需求&#xff0c;本人作了以下尝试&#xff1a; 一、表头的选择 二、明细表格的高度的调整 // 明细表标题行高GridppReport_7.DetailGrid.ColumnTitle.H…

Plant Simulation培训教程-AGV配送物流仿真模块

原创 知行 天理智能科技 2024年12月31日 07:00 浙江 又到年终盘点的时候了&#xff0c;在这里我把之前录制的Plant Simulation培训教程-AGV配送物流仿真模块分享出来&#xff0c;有需要的可以直接联系我。 模型路径通过Marker点搭建&#xff0c;适用于磁条导航、二维码导航等…

【Python 专题】数据结构 树

LeetCode 题目104. 二叉树的最大深度(gif 图解)方法一:后序遍历(DFS)方法二:层序遍历(BFS)872. 叶子相似的树(DFS 遍历)1448. 统计二叉树中好节点的数目(DFS 遍历)437. 路径总和 III(前缀和 + DFS 回溯)1372. 二叉树中的最长交错路径(DFS)236. 二叉树的最近公共…

基于射频开关选择的VNA校准设计

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…

关于协同显著性物体检测的思考

摘要 问题一&#xff1a;什么叫做CoEG-Net框架&#xff1f; CoEG-Net 是一种用于图像分割或其他计算机视觉任务的深度学习框架&#xff0c;具体来说&#xff0c;它主要关注边缘感知和图像细节恢复。CoEG-Net 的全称是 Contextual Edge Guidance Network&#xff0c;其主要创新…

排查JVM的一些命令

查看JVM相关信息的方法 环境&#xff1a; Win10, jdk17 查看端口的Pid netstat -ano | findstr <端口号>列出当前运行的JVM进程 ## 用于输出JVM中运行的进程状态信息。通过jps&#xff0c;可以快速获取Java进程的PID&#xff08;进程标识符&#xff09;&#xff0c; …

【AI应用】Cherry Studio结合deepseek搭建本地知识库

硅基流动注册 前往 硅基官网 &#xff08;https://cloud.siliconflow.cn/i/JUwuNCzb&#xff09;链接带了我的推荐码&#xff0c;注册成功后&#xff0c;您将获得 14R&#xff08;2000W Token&#xff09;&#xff0c;用于配置 Embedding&#xff08;嵌入式模型&#xff09; 新…

第1章大型互联网公司的基础架构——1.11 消息中间件技术

消息队列&#xff08;Message Queue&#xff09;是分布式系统中最重要的中间件之一&#xff0c;在服务架构设计中被广泛使用。 1.11.1 通信模式与用途 消息中间件构建了这样的通信模式&#xff1a; 一条消息由生产者创建&#xff0c;并被投递到存放消息的队列中&#xff1b;…

windows解压多个文件夹内的zip文件脚本

情景引入 不知道大家是否有一个疑问&#xff0c;如果下载到的源码文件是很多个目录&#xff0c;目录里面的项目都是压缩的&#xff0c;那我们该怎么办&#xff1f; 目录结构 目录结构如下 Test ├── 1 │ └── 1.zip └── 2 └── 2.zip 执行脚本 先cd到Test下&am…

文件和目录的操作-8

文章目录 1.IO流2.文件流操作with语句3.文件和文件夹的操作4.案例1.IO流 通过“流”的形式允许计算机程序使用相同的方式来访问不同的输入/输出源。stream是从源(source)到接收目标的(sink)有序数据。如果把输入/输出源比作“水桶”,那流就是“管道” 文件流:就是源或者…