寒假学习笔记【匠心制作,图文并茂】——1.20拓扑、强连通分量、缩点

article/2025/6/19 15:07:44

文章目录

  • 前言
  • 拓扑排序
    • 拓扑排序是怎么运作的
    • 拓扑排序的好处
  • 强连通分量
    • 强连通是什么
    • 强连通分量是什么
    • 如何求 SCC
  • 缩点

前言

更新的稍微有点晚……

因为强连通分量这一块难学且知识点多,学习时间久了亿点,所以直到现在才更新。

拓扑排序

OI-Wiki 是这么定义拓扑排序的:

拓扑排序(Topological sorting)要解决的问题是如何给一个有向无环图的所有节点排序。

这个感觉不是很像定义,更像是它在干什么,我们来重新定义一下拓扑排序:

拓扑排序是一种将一个有向无环图(Directed Acyclic Graph,简称 DAG)按照一定顺序对所有节点进行排序的算法。

注意:只能在有向无环图上跑拓扑。

拓扑排序是怎么运作的

接下来我就要开始操作了,注意步骤:

  1. 取出图中入度为 0 0 0 的点,加入队列。
  2. 取出队头,将与队头相连的点的入度全减 1 1 1
  3. 将图上入度为 0 0 0 的点加入队列。
  4. 重复执行 2 2 2 3 3 3 操作。
  5. 想你的诗和远方吧……

没看明白的观众就请看下面的模拟,看明白了的,也还是看一下案例吧。

我们先随机生成一个 DAG:

首先按照第一步:取出图中入度为 0 0 0 的点并放入队列(如果你连入度是啥都不知道,那你就不应该出现在这)。图中入度为 0 0 0 的点不就是 1 1 1 吗?此时队列中有一个 1 1 1

然后进行第二步:取出队头,将与队头相连的点的入度全减 1 1 1。队头就是 1 1 1,那么我们取出 1 1 1 并将所有与队头相连的点入度减 1 1 1,整张图就相当于变成了这样:

队列此时为空。

然后进行第三步:将图上入度为 0 0 0 的点加入队列。图上入度为 0 0 0 的点有 2 2 2 3 3 3,所以把它们加入队列,于是队列变成了这样:

然后就是重复执行以上操作……

最后,我们得到的拓扑序就是这三种中的一种:1 2 3 6 4 51 3 2 4 6 51 3 2 6 4 5

不难看出,一张图的拓扑序可以有很多个。所以这类题一般都是 SPJ(Special Judge)。

以 洛谷 B3644 为例,代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,rd[106];
vector<int>v[106];
queue<int>q;
signed main()
{cin>>n;for(int x,i=1;i<=n;i++)//统计入度{while(cin>>x&&x){v[i].emplace_back(x);rd[x]++;}}for(int i=1;i<=n;i++)//第一步{if(rd[i]==0){q.emplace(i);}}while(!q.empty())//第二、三步{int x=q.front();q.pop();cout<<x<<" ";for(auto i:v[x]){rd[i]--;if(rd[i]==0){q.emplace(i);}}}return 0;
}

拓扑排序的好处

拓扑排序是一种对图的排序,这种排序可以将二维的图转化成一维的线性数组,这更便于我们跑 DP。

强连通分量

在认识强连通分量之前,我们先来认识一下强连通。

强连通是什么

强连通指如果在一个有向图中,任意两个节点都可以连通,那么我们称之为强连通。

强连通分量是什么

强连通分量(Strongly Connected Components,简称 SCC)指极大的强连通子图。意思就是说对于一个有向图,如果它的其中一个子图是强连通,并且往这个子图里加任何的有向图上的其他点和边它都不再是一个强连通子图,那么我们称之为强连通分量。

比如这个图:

其中点 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 就构成了一个强连通子图,但如果我往这里面加 5 , 6 5,6 5,6 中的任何一个点,这个子图就不再是强连通子图,所以点 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 就构成了一个强连通分量。

(为了防止作者被累死,后面所有的“强连通分量”都简写为“SCC”。)

如何求 SCC

这里我讲一下最出名的 Tarjan 算法。

Tarjan 的思路其实很简单,就是把一个图看作一棵奇怪的树。

这里我们随机生成一颗树:

但是我们原本是一张图不是一颗树啊,所以这里必然有一些奇怪的边出来:

其中这些边我们大致可以分为四类:树边(黑色线)、前向边(红色线)、返祖边(绿色线)、横叉边(蓝色线)。

相信大家通过这张图一定能很好的理解这四种边的定义。

树边:构成树的边。

前向边:从某个根节点到其子节点的边。

返祖边:从某个子节点到其根节点的边。

横叉边:从某个子节点到另一个与它毫无关系的子节点的边。

这里我说明一下:所有的横叉边都只会是向左的。因为所有可能向右的横叉边都会被拿来当成树边,那么另一条边就成了向左的横叉边。

因为我们要找 SCC,也就相当于要在这张图上找环(因为一个环就是一个 SCC),那我们看除了树边以外的其他特殊边那些可能产生环呢?

答案很简单,就是返祖边和横叉边(从图上也能看出来)。

返祖边容易形成环是因为返祖边能返回去,而树边则可以走下来,这样就形成了一个环。横叉边容易形成环是因为它可以连接左右两棵子树,更容易搭建起环,它一般不会直接形成环,而会间接的促成环。

接着我们来看一下这两种边都有什么共同点。不难发现,这两种边的终点都是比起点先被扫描的点。

到这里,Tarjan 老爷子就想通了一切:定义两个数组 dfn[x]low[x] 表示第 x x x 个节点被扫描时的编号与 x x x 所能到达的所有点中 dfn 最小的点。

这么定义有什么好处呢?好处可大了!我们假设一个节点与它的子树形成了一个 SCC,那么它以及它的所有子树都不会通过返祖边与横叉边与其他点相连,而且一定会有返祖边与这个根节点相连,所以这个 SCC 里的所有点的 low 都是这个根节点的 dfn,等到递归回到根节点时一查,发现 low==dfn,这下就说明它的所有子节点都无法通过返祖边与横叉边与其他点相连,那就说明这里就是一个 SCC,把它及它的子节点全部保存在一起就行了。

为了方便保存,Tarjan 老爷子还十分聪明的使用了桟来记录当前的访问过的点,这样取出 SCC 时就只需要一直弹出栈顶了。

以 洛谷 B3609 为例题,代码如下:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std;
code by plh
int n,m,cnt,ti,dfn[10006],low[10006],ins[10006],b[10006],vis[10006],belong[10006];
vector<int>v[10006],vv[10006];
stack<int>st;
void tarjan(int x)
{dfn[x]=low[x]=++ti;//记录编号st.emplace(x);ins[x]=1;for(auto i:v[x]){if(!dfn[i]){tarjan(i);low[x]=min(low[x],low[i]);//更新}else if(ins[i]){low[x]=min(low[x],dfn[i]);}}if(low[x]==dfn[x])//找到 SCC{cnt++;while(1){int u=st.top();st.pop();ins[u]=0;belong[u]=cnt;//记录下来每个点属于哪个 SCC(主要是为了缩点)b[u]=cnt;vv[cnt].emplace_back(u);//记录下来有哪些点if(u==x){break;}}}
}
signed main()
{cin>>n>>m;for(int x,y,i=1;i<=m;i++){cin>>x>>y;v[x].emplace_back(y);}for(int i=1;i<=n;i++){if(!dfn[i]){tarjan(i);}}for(int i=1;i<=cnt;i++){sort(vv[i].begin(),vv[i].end());}cout<<cnt<<endl;for(int i=1;i<=n;i++){if(!vis[b[i]]){for(auto j:vv[b[i]]){cout<<j<<" ";}cout<<endl;vis[b[i]]=1;}}return 0;
}

缩点

讲完强连通分量,我们就可以来好好讲讲缩点了。

缩点,就是把一个 SCC 看作一个点并构建新图,这样做的好处就在于它会形成一个新的 DAG(并且还能优化时间复杂度),便于我们跑拓扑与 dfs。

代码其实很简单,就是用上述的 belong 数组进行操作:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std;
code by plh
int n,m,ti,cnt,dis[10006],rd[10006],a[10006],b[10006],belong[10006],dfn[10006],low[10006],ins[10006];
vector<int>v[10006],vv[10006];
stack<int>st;
map<pair<int,int>,bool>mp;
queue<int>q;
void tarjan(int x)//跑 tarjan
{dfn[x]=low[x]=++ti;st.emplace(x);ins[x]=1;for(auto i:v[x]){if(!dfn[i]){tarjan(i);low[x]=min(low[x],low[i]);}else if(ins[i]){low[x]=min(low[x],dfn[i]);}}if(low[x]==dfn[x]){++cnt;while(1){int u=st.top();st.pop();ins[u]=0;belong[u]=cnt;b[cnt]+=a[u];if(u==x){break;}}}
}
int topsort()//拓扑排序
{for(int i=1;i<=cnt;i++){if(!rd[i]){q.emplace(i);dis[i]=b[i];}}while(!q.empty()){int x=q.front();q.pop();for(auto i:vv[x]){dis[i]=max(dis[i],dis[x]+b[i]);rd[i]--;if(!rd[i]){q.emplace(i);}}}int ans=0;for(int i=1;i<=cnt;i++){ans=max(ans,dis[i]);}return ans;
}
signed main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1,x,y;i<=m;i++){cin>>x>>y;v[x].emplace_back(y);}for(int i=1;i<=n;i++){if(!dfn[i]){tarjan(i);}}for(int i=1;i<=n;i++)//建新图{for(auto j:v[i]){if(belong[i]!=belong[j]&&!mp[make_pair(belong[i],belong[j])])//这里有时需要判重,有时又不需要,主要是看重边会不会有影响//如果只是单纯跑拓扑是没有什么影响的,但如果要做与边相关的运算就有影响了{vv[belong[i]].emplace_back(belong[j]);rd[belong[j]]++;mp[make_pair(belong[i],belong[j])]=1;}}}cout<<topsort();return 0;
}

http://www.hkcw.cn/article/rtxdWyivfo.shtml

相关文章

git下载和安装(完整版)

目录 一&#xff0c;官网下载 二, 安装步骤 1 双击直接安装【版本为64位系统的】 2 点击Next 3 点击Finish完成安装&#xff0c;验证安装&#xff0c;找一个桌面空白处&#xff0c;右键出现下列窗口 4 检验是否成功 一&#xff0c;官网下载 git官网地址&#xff1a;Gi…

系统思考:化繁为简的艺术

系统思考&#xff0c;其实是一门化繁为简的艺术。当我们能够把复杂的问题拆解成清晰的核心以及更加简单&#xff0c;从而提升团队的思考品质和行动品质&#xff0c;发挥最大的合力。 每个公司都想在某方面成为最优秀的&#xff0c;但是实际上具有穿透性的洞察力和摆脱虚荣心的清…

【Kotlin】简介变量类接口

【Kotlin】简介&变量&类&接口 【Kotlin】数字&字符串&数组&集合 文章目录 Kotlin_简介&变量&类&接口Kotlin的特性Kotlin优势创建Kotlin项目变量变量保存了指向对象的引用优先使用val来避免副作用 编译期常量后端变量Backing Fields后端属性…

8086 处理器 Flags 标志位全解析:CPU 的 “晴雨表” 与 “遥控器”总结:

引入&#xff1a; 你是否好奇&#xff0c;当 CPU 执行一条加法指令时&#xff0c;如何自动判断结果是否超出范围&#xff1f;当程序跳转时&#xff0c;如何快速决定走哪条分支&#xff1f;甚至在调试程序时&#xff0c;为何能让 CPU “一步一停”&#xff1f;这一切的答案&…

uniapp uni-id Error: Invalid password secret

common文件夹下uni-config-center文件夹下新建uni-id,新建config.json文件 复制粘贴以下代码&#xff0c;不要自己改&#xff0c;格式容易错 {"passwordSecret": [{"type": "hmac-sha256","version": 1}], "passwordStrength&qu…

从0到1上手Trae:开启AI编程新时代

摘要&#xff1a;字节跳动 2025 年 1 月 19 日发布的 Trae 是一款 AI 原生集成开发环境工具&#xff0c;3 月 3 日国内版推出。它具备 AI 问答、代码自动补全、基于 Agent 编程等功能&#xff0c;能自动化开发任务&#xff0c;实现端到端开发。核心功能包括智能代码生成与补全、…

云计算和服务器

一、云计算概述 ICT是世界电信协会在2001年的全球性会议上提出的综合性概念&#xff0c;ICT分为IT和CT&#xff0c;IT(information technology)信息技术&#xff0c;负责对数据生命周期的管理&#xff1b;CT(communication technology)&#xff0c;负责数据的传输管理。 CT技术…

云计算与分布式系统:从零开始构建!

🏆本文收录于「编程与技术实战」专栏,此专栏涵盖了C/C++编程、人工智能、数据结构、机器学习等技术领域的内容,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 本文目录: 前言云计算概念与架构什么是云计算?…

零基础入门haproxy七层代理

文章目录 haproxy的安装和服务信息global配置proxies配置defaults配置frontend配置backed配置listen配置 socat工具 haproxy算法静态算法动态算法其他算法 高级功能及配置案例基于cookie会话保持HAProxy状态页IP透传四层IP透传七层IP透传 ACL匹配规范基于HTTP请求头部的匹配精确…

清华大学杨诚最新Nature子刊

研究背景 电致变色&#xff08;Electrochromic, EC&#xff09;器件作为一种新兴的节能和调光技术&#xff0c;展示了动态调节光和热透射率的能力&#xff0c;是未来智能建筑、智能汽车天窗以及智能可穿戴设备的重要技术组成。然而&#xff0c;传统的EC器件的商业化面临着诸如…

【课程笔记】华为 HCIP-Cloud Computing 云计算08:OpenStack网络管理

OpenStack网络管理 目录 OpenStack网络管理 一、Linux网络虚拟化基础 二、Neutron简介 三、Neutron概念 四、Neutron架构 五、Neutron典型操作及流程 六、Neutron网络流量分析 一、Linux网络虚拟化基础 Linux下的网络虚拟化本质就是将原本物理网络中的网卡、物理虚拟机…

使用异步编程模型结合资源预测算法优化云计算环境下的任务调度与能耗管理技术详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用异步编程模型结合资源预测算法优化云计算环境下的任务调度与能耗管理技术详解 使用异步编程模型结合资源预测算法优化云计算…

云计算——云计算关键技术

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 前言 一.云计算关键技术 1.虚拟化技术 2.分布式数据存储技术 &#xff08;1&…

2024广东省职业技能大赛云计算——私有云(OpenStack)平台搭建

OpenStack搭建 前言 搭建采用双节点安装&#xff0c;即controller控制节点和compute计算节点。 CentOS7 系统选择 2009 版本&#xff1a;CentOS-7-x86_64-DVD-2009.iso 可从阿里镜像站下载&#xff1a;https://mirrors.aliyun.com/centos/7/isos/x86_64/ OpenStack使用竞赛培…

云计算时代的运维: 职业发展方向与岗位选择

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

如何找到一条适合自己企业的发展之路?

一个创业型的企业&#xff0c;开始就需要面向市场&#xff0c;通过自己的服务或产品&#xff0c;帮助用户解决问题&#xff0c;为客户创造价值&#xff0c;通过为客户创造的价值&#xff0c;出创造一定的的现金流&#xff0c;让企业存活下来&#xff01; 企业的运营过程中&…

Github 热点 Github 热点 Syncthing:多台设备,持续同步文件,安全同步,隐私无忧!

今日推荐&#xff1a;syncthing Syncthing是一个开源、安全且易于使用的持续文件同步工具&#xff0c;可在多台计算机之间自动同步文件。 1prompt-eng-interactive-tutorial 今日星标 1211 总星标数 4273 主要语言 Jupyter Notebook https://github.com/anthropics/prompt-e…

K 值选对,准确率翻倍:KNN 算法调参的黄金法则

目录 一、背景介绍 二、KNN 算法原理 2.1 核心思想 2.2 距离度量方法 2.3 算法流程 2.4算法结构&#xff1a; 三、KNN 算法代码实现 3.1 基于 Scikit-learn 的简单实现 3.2 手动实现 KNN&#xff08;自定义代码&#xff09; 四、K 值选择与可视化分析 4.1 K 值对分类…

线程(上)【Linux操作系统】

文章目录 线程概念及其相关知识线程的概念及一些重要认识重要认识Linux中线程的实现Linux中的被调度的执行流是被task_struct描述的 线程是如何瓜分进程的代码和数据的&#xff1f;对于数据&#xff1a;对于代码&#xff1a; 线程的优点线程的缺点线程调度细节调度&#xff1a;…

定制开发开源AI智能名片S2B2C商城小程序:数字营销时代的话语权重构

摘要&#xff1a;在数据驱动的数字营销时代&#xff0c;企业营销话语权正从传统媒体向掌握用户数据与技术的平台转移。本文基于“数据即权力”的核心逻辑&#xff0c;分析定制开发开源AI智能名片S2B2C商城小程序如何通过技术赋能、场景重构与生态协同&#xff0c;帮助企业重构营…