【图论】有向图的强连通分量

news2024/12/29 9:55:58

算法提高课笔记(本篇未更新完 还有俩例题)

文章目录

  • 理论基础
    • SCC板子
  • 例题
    • 受欢迎的牛
      • 题意
      • 思路
      • 代码
    • 学校网络
      • 题意
      • 思路
      • 代码

理论基础

什么是连通分量?

对于一个有向图,分量中任意两点u,v,必然可以从u走到v,且从v走到u,这样的分量叫做连通分量

如果一个连通分量加上任意一个点都不是连通分量了,就把它叫做 强连通分量

强连通分量的主要作用:将任意一个有向图转化成一个有向无环图即拓扑图(通过缩点的方式),缩点就是将所有连通分量缩成一个点

如何求强连通分量呢?

按照DFS的顺序搜,我们可以将边分为以下四类:

  1. 树枝边:(x, y),x是y的父结点
  2. 前向边:(x, y),x是y的祖先结点
  3. 后向边:(x, y),y是x的祖先结点
  4. 横叉边:往之前搜过的其他点搜
    在这里插入图片描述
    怎么判断一个点是否在强连通分量中?
  • 情况一:存在后向边指向祖先结点
  • 情况二:先走到横叉边,横叉边再走到祖先结点

(反正一定可以走到某个祖先)

基于这个想法—— Tarjon 算法求强连通分量(SCC)

先给每个结点按照 DFS 访问顺序确定一个时间戳,时间戳越小说明越先访问到

dfn[u]:遍历到 u 的时间戳
low[u]:从 u 开始走,能遍历到的最小时间戳
id[i]:i 所在连通分量的编号

u是其所在强连通分量的最高点 <-> dfb[u] == low[u]

SCC板子

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp; // 先将dfn和low都初始化为时间戳
    stk.push(u), in_stk[u] = true; // u加入栈中

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; // 取出u的所有邻点j
        if (!dfn[j]) // 如果j还没被遍历
        {
            tarjan(j);
            low[u] = min(low[u], low[j]); // 用low[j]更新low[u]
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]); // 如果j已入栈 则用dfn[j]更新low[u]
    }

    if (dfn[u] == low[u]) // 如果该点是所在强连通分量的最高点
    {
        ++ scc_cnt; // 强连通分量数量加一
        int y;
        do {
            y = stk.top(); // 取出栈顶元素
            stk.pop();
            in_stk[y] = false;

            id[y] = scc_cnt; // 标记每个点所在的连通分量编号
        } while (y != u); // 直到取到此连通分量的最高点为止
    }
}

缩点的步骤:

  1. 遍历所有点 i
  2. 遍历 i 的所有邻点 j
  3. 如果 i 和 j 不在同一个连通分量中,就加一条新边 id[i]->id[j]
for (int i = 1; i <= n; i ++ )
        for (int j = h[i]; ~j; j = ne[j])
        {
            int k = e[j]; // 遍历i的所有邻点k
            int a = id[i], b = id[k]; // 记录ik所在连通分量编号
            if (a != b) dout[a] ++ ; // 如果ik不在同一个连通分量 就在两个连通分量之间连一条i指向k的边
        }

做完tarjon后,连通分量编号递减的顺序一定就是拓扑序

例题

受欢迎的牛

原题链接

每一头牛的愿望就是变成一头最受欢迎的牛。

现在有 N 头牛,编号从 1 到 N,给你 M 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。

这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。

你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。

输入格式

第一行两个数 N,M;

接下来 M 行,每行两个数 A,B,意思是 A 认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。

输出格式

输出被除自己之外的所有牛认为是受欢迎的牛的数量。

数据范围

1 ≤ N ≤ 104 , 1≤N≤104, 1N104,
1 ≤ M ≤ 5 × 104 1≤M≤5×104 1M5×104

输入样例

3 3
1 2
2 1
2 3

输出样例

1

样例解释

只有第三头牛被除自己之外的所有牛认为是受欢迎的。

题意

一头牛会欢迎另一头牛,这种欢迎是有传递性的,现给出多对欢迎关系,问有几头牛是被其余所有牛欢迎的

思路

先对连通分量进行缩点操作

之后分析,如果有唯一一个连通分量满足出度为0,那么这个连通分量内的所有点都可以由其余任意点到达,答案就是这个连通分量内点的个数,如果这样的连通分量超过一个就不满足条件

代码

#include <bits/stdc++.h>

using namespace std;

const int  N = 10010, M = 50010;

int n, m;
int h[N], ne[M], e[M], idx;
int dfn[N], low[N], timestamp;
stack<int> stk;
bool in_stk[N]; // 存储点是否入栈
int id[N], scc_cnt, Size[N];
int dout[N]; // 连通分量的出度

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp; // 先将dfn和low都初始化为时间戳
    stk.push(u), in_stk[u] = true; // u加入栈中

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; // 取出u的所有邻点j
        if (!dfn[j]) // 如果j还没被遍历
        {
            tarjan(j);
            low[u] = min(low[u], low[j]); // 用low[j]更新low[u]
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]); // 如果j已入栈 则用dfn[j]更新low[u]
    }

    if (dfn[u] == low[u]) // 如果该点是所在强连通分量的最高点
    {
        ++ scc_cnt; // 强连通分量数量加一
        int y;
        do {
            y = stk.top(); // 取出栈顶元素
            stk.pop();
            in_stk[y] = false;

            id[y] = scc_cnt; // 标记每个点所在的连通分量编号
            Size[scc_cnt] ++ ; // 更新此连通分量中的点个数
        } while (y != u); // 直到取到此连通分量的最高点为止
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }

    for (int i = 1; i <= n; i ++ )
        if (!dfn[i]) tarjan(i);

    for (int i = 1; i <= n; i ++ )
        for (int j = h[i]; ~j; j = ne[j])
        {
            int k = e[j]; // 遍历i的所有邻点k
            int a = id[i], b = id[k]; // 记录ik所在连通分量编号
            if (a != b) dout[a] ++ ; // 如果ik不在同一个连通分量 就在两个连通分量之间连一条i指向k的边
        }

    int zeros = 0, sum = 0;
    for (int i = 1; i <= scc_cnt; i ++ )
        if (!dout[i]) // 如果当前连通分量出度为0
        {
            zeros ++ ; // 出度为0的连通分量个数加一
            sum += Size[i]; // 更新出度为0的连通分量中点的个数
            if (zeros > 1) // 如果出度为0的连通分量个数超过一个 说明没有一头牛被所有牛喜欢
            {
                sum = 0;
                break;
            }
        }

    cout << sum << '\n';
}

学校网络

原题链接

一些学校连接在一个计算机网络上,学校之间存在软件支援协议,每个学校都有它应支援的学校名单(学校 A 支援学校 B,并不表示学校 B 一定要支援学校 A)。

当某校获得一个新软件时,无论是直接获得还是通过网络获得,该校都应立即将这个软件通过网络传送给它应支援的学校。

因此,一个新软件若想让所有学校都能使用,只需将其提供给一些学校即可。

现在请问最少需要将一个新软件直接提供给多少个学校,才能使软件能够通过网络被传送到所有学校?

最少需要添加几条新的支援关系,使得将一个新软件提供给任何一个学校,其他所有学校就都可以通过网络获得该软件?

输入格式

第 1 行包含整数 N,表示学校数量。

第 2…N+1 行,每行包含一个或多个整数,第 i+1 行表示学校 i 应该支援的学校名单,每行最后都有一个 0 表示名单结束(只有一个 0 即表示该学校没有需要支援的学校)。

输出格式

输出两个问题的结果,每个结果占一行。

数据范围

2 ≤ N ≤ 100 2≤N≤100 2N100

输入样例

5
2 4 3 0
4 5 0
0
0
1 0

输出样例

1
2

题意

给出一张图:

  • 问题一:至少从多少个点出发能够遍历完图上所有点
  • 问题二:至少加多少条边能让图的强连通分量就是自身

思路

设入度为0的连通分量个数为a,出度为0的连通分量个数为b

问题一就是问a的大小

问题二就是问ab中较大的值(需要特判一下如果只有一个强连通分量,就不需要加边,输出0即可 )
举个栗子 不具体证明了:在这里插入图片描述

代码

#include <bits/stdc++.h>

using namespace std;

const int  N = 110, M = 10010;

int n, m;
int h[N], ne[M], e[M], idx;
int dfn[N], low[N], timestamp;
stack<int> stk;
bool in_stk[N]; // 存储点是否入栈
int id[N], scc_cnt;
int din[N], dout[N]; // 连通分量的入度和出度

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp; // 先将dfn和low都初始化为时间戳
    stk.push(u), in_stk[u] = true; // u加入栈中

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; // 取出u的所有邻点j
        if (!dfn[j]) // 如果j还没被遍历
        {
            tarjan(j);
            low[u] = min(low[u], low[j]); // 用low[j]更新low[u]
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]); // 如果j已入栈 则用dfn[j]更新low[u]
    }

    if (dfn[u] == low[u]) // 如果该点是所在强连通分量的最高点
    {
        ++ scc_cnt; // 强连通分量数量加一
        int y;
        do {
            y = stk.top(); // 取出栈顶元素
            stk.pop();
            in_stk[y] = false;

            id[y] = scc_cnt; // 标记每个点所在的连通分量编号
        } while (y != u); // 直到取到此连通分量的最高点为止
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ) // 建图
    {
        int t;
        while (cin >> t, t) add(i, t);
    }

    for (int i = 1; i <= n; i ++ )
        if (!dfn[i]) tarjan(i);

    for (int i = 1; i <= n; i ++ )
        for (int j = h[i]; ~j; j = ne[j])
        {
            int k = e[j]; // 遍历i的所有邻点k
            int a = id[i], b = id[k]; // 记录ik所在连通分量编号
            if (a != b) // 如果ik不在同一个连通分量 就在两个连通分量之间连一条i指向k的边
            {
                dout[a] ++ ;
                din[b] ++ ;
            }
        }

    int a = 0, b = 0;
    for (int i = 1; i <= scc_cnt; i ++ )
    {
        if (!din[i]) a ++ ; // 记录入度为0的点个数
        if (!dout[i]) b ++ ; // 记录出度为0的点个数
    }

    cout << a << '\n';
    if (scc_cnt == 1) cout << 0 << '\n';
    else cout << max(a, b) << '\n';
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跑步运动耳机哪个牌子好、推荐几款专业跑步耳机

跑步是一项简单的运动&#xff0c;只要交替迈左右腿就能进行。然而&#xff0c;跑步也可能会变得单调乏味。即使是意志坚定、热爱跑步的人&#xff0c;在这漫长的过程中也会感到乏味&#xff0c;更不用说像你我这样的普通跑者了。音乐能够让跑步这项运动变得有趣起来&#xff0…

前端自适应瀑布流布局

JS案例自适应瀑布流 &#x1f31f;效果预览 &#x1f31f;什么是瀑布流 &#x1f31f;制作思路 &#x1f31f;具体实现 页面结构 js代码实现 &#x1f31f;写在最后 &#x1f31f;效果预览 前端自适应瀑布流效果预览 &#x1f31f;什么是瀑布流 瀑布流&#xff0c;又…

prize_p1

文章目录 解题过程代码审计思路问题解决数组绕过preg_match__destruct的触发修改phar文件以及签名phar://支持的后缀 题解方法一&#xff08;数组绕过&#xff09;方法二&#xff08;gzip绕过&#xff09; 解题过程 源代码 <META http-equiv"Content-Type" conte…

3D人体生成名人堂

一、马普所认知系统实验室。 Perceiving Systems - Max Planck Institute for Intelligent SystemsUsing computer vision, computer graphics, and machine learning, we teach computers to see people and understand their behavior in complex 3D scenes. We are located…

《Prometheus 监控实践:从零到英雄》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

【服务器 | 宝塔】宝塔面板卸载重装教程:清理删除宝塔面板并重新开始

宝塔面板卸载重装怎么操作?我们很多用户可能安装宝塔之后会经常看一下有没有新版本&#xff0c;如果有新版直接右上角 宝塔面板卸载重装怎么操作?我们很多用户可能安装宝塔之后会经常看一下有没有新版本&#xff0c;如果有新版直接右上角”更新”升级一下版本就可以了&#…

1-4 AUTOSAR方法论

总目录——AUTOSAR入门详解AUTOSAR入门详解目录汇总&#xff1a;待续中。。。https://xianfan.blog.csdn.net/article/details/132818463 目录 一、前言 二、方法论 三、单个ECU开发流程 一、前言 汽车生产供应链上有以下角色&#xff1a;OEM、TIER1、TIER2&#xff0c;其主…

提示msvcr120.dll丢失怎样修复呢?全面分析msvcr120.dll解决方法

在计算机使用过程中&#xff0c;可能会遇到 msvcr120.dll 丢失的问题。msvcr120.dll 是 Microsoft Visual C Redistributable 的一部分&#xff0c;是一个动态链接库文件&#xff08;DLL&#xff09;&#xff0c;用于支持运行在 Windows 操作系统上使用了 Microsoft Visual C 2…

9.12|day 5|day 44 |完全背包| 518. 零钱兑换 II | 377. 组合总和 Ⅳ

● 完全背包 主要是看清01背包和完全背包的区别 //01背包 for(int i 0;i<weight.size();i){ for(int j bagWeight;j>weight[i];j--){dp[j] Math.max(dp[j],dp[j-weight[i]]value[i]); } } //完全背包 for(int i 0;i<weight.size();i){for(int j weight[i];j<…

数据结构——排序算法——冒泡排序

冒泡排序1 void swap(vector<int> arr, int i, int j) {int temp arr[i];arr[i] arr[j];arr[j] temp;}void bubbleSort1(vector<int> arr) {for (int i 0; i < arr.size() - 1; i){for (int j 0; j < arr.size() - 1 - i; j){if (arr[j] > arr[j 1…

c++ 学习 之 常函数 和 常对象

前言 常函数 成员函数后加 const 我们可以称这个函数为 常函数 常函数内不可以修改成员属性 成员属性声明时加关键字 mutable 后&#xff0c;在常函数中依然可以修改 常对象 常对象 声明对象前加 const 称该对象为常对象 常对象只能调用常函数 正文 常函数 class Person…

《深入分布式追踪:OpenTracing 实践手册》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

【面试题精讲】你知道MySQL中有哪些隔离级别吗

uuid: 7ae741a0-517a-11ee-93e3-6f2b73edb0c7 title: 【面试题精讲】你知道MySQL中有哪些隔离级别吗 tags: [MySQL, 隔离级别, 脏读, 幻读, 不可重复读] categories: [技术文章, 后端技术, 系列文章, 面试题精讲] abbrlink: 14595062 date: 2023-09-12 22:41:38 有时博客内容会…

Linux CentOS7修改命令行提示符

在CentOS操作系统中&#xff0c;命令和文件是我们与计算机进行交互的重要方式之一。有时候我们可能需要对某些命令、变量或文件进行修改&#xff0c;以满足特定的需求或提高工作效率。 本人在文章《Linux CentOS7命令及命令行》中对命令行提示符的修改作了初步介绍&#xff0c…

抖音中文点选验证码识别方案

最近研究了一下抖音中文点选验证码的识别&#xff0c;居然正确率高达98%。 首先我们来看一下效果 一、识别方法 1、数据集准备 我们需要借助爬虫去下载大量的验证码图片。这些有两种图片&#xff0c;一种是小图&#xff0c;包含需要点选的中文和顺序&#xff0c;还有一种是大…

记录一次部署Hugo主题lotusdocs到Github Pages实践

引言 随着开源项目的越来越复杂&#xff0c;项目文档的重要性日渐突出。一个好的项目要有一个清晰明了的文档来帮助大家使用。最近一直有在找寻一个简洁明了的文档主题来放置项目的各种相关文档。最终找到这次的主角&#xff1a;Lotus Docs 基于Hugo的主题。Lotus Docs的样子&…

【LeetCode题目详解】第九章 动态规划part16 583. 两个字符串的删除操作 ● 72. 编辑距离 ● 编辑距离总结篇 (day56补)

本文章代码以c为例&#xff01; 本文章转自代码随想录 一、力扣第583题&#xff1a;两个字符串的删除操作 题目&#xff1a; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 示例 1…

[Qt]窗口

文章摘于 爱编程的大丙 文章目录 1. 基础窗口类1.1 QWidget1.1.1 设置父对象1.1.2 窗口位置1.1.3 窗口尺寸1.1.4 窗口标题和图标1.1.5 信号1.1.6 槽函数 1.2 QDialog1.2.1 常用API1.2.2 常用使用方法 1.3 QDialog的子类1.3.1 QMessageBox1.3.1.1 API - 静态函数1.3.1.2 测试代码…

第10章_freeRTOS入门与工程实践之同步互斥与通信

本教程基于韦东山百问网出的 DShanMCU-F103开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id724601559592 配套资料获取&#xff1a;https://rtos.100ask.net/zh/freeRTOS/DShanMCU-F103 freeRTOS系列教程之freeRTOS入…

HBASE知识点

HBASE是什么&#xff1f; 高可靠、高性能、面向列、可伸缩、实时读写的分布式数据库。利用HDFS作为其文件存储系统&#xff0c;利用MapReduce来处理HBase中的海量数据。利用Zookeeper作为其分布式协同服务。用于存储非结构化和半结构化的松散数据。 HBase数据模型 RowKey: 唯…