有向图的强连通分量

news/2025/2/13 10:40:55/文章来源:https://www.cnblogs.com/ybC202444/p/18712659

前置

连通分量定义:在\(u\)\(v\)在一个强连通分量中,则存在\(u\)\(v\)的路径和\(v\)\(u\)的路径。

强连通分量scc:极大连通分量

作用:通过缩点将有向图转换为有向无环图DAG(拓扑图),将题目变得好做。

:树枝边、前向边、后向边、横叉边

判断一个点是否在某个scc中

  1. 存在一条后向边指向祖先节点。
  2. 先走到横叉边,横叉边再走到祖先节点。

注意:对于前向边不会成为判断形成回路的关键。

tarjan算法求强连通分量SCC

对于每个点定义两个时间戳\(dfn[u]\)表示遍历到u的时间,\(low[u]\)表示从\(u\)开始走所能遍历到的最小时间戳。

u是其所在的强连通分量的最高点,等价于\(dfn[u] == low[u]\)

连通分量编号递减的顺序一定是拓扑序。

受欢迎的牛

#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 50005;
int n, m, head[N], cnt = -1, tot = 0, dfn[N], low[N];
int scc_cnt = 0, id[N], out[N], siz[N];
stack<int> stk;
bool in_stk[N];
struct edge
{int to, nxt;
}e[M];
void add_edge(int u, int v)
{e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;return ;
}void tarjan(int x)
{dfn[x] = low[x] = ++ tot, stk.push(x), in_stk[x] = true;for (int i = head[x]; i != -1; i = e[i].nxt){int y = e[i].to;if(!dfn[y]){tarjan(y);low[x] = min(low[x], low[y]);}else if(in_stk[y]) low[x] = min(low[x], dfn[y]);}if(low[x] == dfn[x]){int y = 0;scc_cnt ++;do{y = stk.top(), stk.pop();siz[scc_cnt] ++;id[y] = scc_cnt;in_stk[y] = false;}while(y != x);}return ;
} 
void solve()
{
//	printf("%d %d!\n", scc_cnt, siz[1]);for (int x = 1; x <= n; ++x){for (int j = head[x]; j != - 1; j = e[j].nxt){int y = e[j].to;if(id[x] != id[y]) out[id[x]] ++;}}int c = 0, ans = 0;for (int i = 1; i <= scc_cnt; ++ i){if(!out[i]) c ++, ans = siz[i];}if(c == 1) printf("%d", ans);else printf("0");return ;
}
int main()
{memset(head, -1, sizeof(head));scanf("%d %d", &n, &m);for (int i = 1; i <= m; ++ i){int u, v;scanf("%d %d", &u, &v);add_edge(u, v);}for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);solve();
}

学校网络

#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = N * N;
struct edge
{int to, nxt;
}e[M];
int head[N], cnt = -1; 
int n, dfn[N], low[N], timestamp = 0, scc_cnt = 0, id[N], out[N];
stack<int> stk;
bool in_stk[N]; 
void add_edge(int u, int v)
{e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;return ;
}
void tarjan(int x)
{low[x] = dfn[x] = ++ timestamp;stk.push(x), in_stk[x] = true;for (int i = head[x]; i != -1; i = e[i].nxt){int y = e[i].to;if(!dfn[y]){tarjan(y);low[x] = min(low[x], low[y]);}else if(in_stk[y]) low[x] = min(low[x], dfn[y]); }if(low[x] == dfn[x]){int y;scc_cnt ++;do{y = stk.top(), stk.pop();id[y] = scc_cnt;in_stk[y] = false;}while(y != x);}return ;
}
int in[N];
void solve()
{for (int x = 1;  x <= n; ++x){for (int i = head[x]; i != -1; i = e[i].nxt){int y = e[i].to;if(id[x] != id[y]) in[id[y]] ++, out[id[x]] ++;}}int sum = 0, tot = 0;for (int i = 1; i <= scc_cnt; ++ i){if(!in[i]) sum ++; if(!out[i]) tot ++;}printf("%d\n", sum);if(scc_cnt != 1) printf("%d", max(tot, sum));else printf("0");return ;
}
int main()
{memset(head, -1, sizeof head);scanf("%d", &n);for (int i = 1; i <= n; ++ i){int x;while(true){scanf("%d", &x);if(x == 0) break;add_edge(i, x);}}for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);solve();return 0;
}

最大半连通子图

#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 1000005;
int n, m, p, cnt = -1, timestamp = 0, scc_cnt = 0, low[N], dfn[N], f[N], g[N], head[N], siz[N], id[N]; 
int in[N];
stack<int> stk;
bool in_stk[N], vis[N];
struct edge
{int to, nxt;
}e[M];
void add_edge(int u, int v)
{e[++ cnt].nxt = head[u], e[cnt].to = v, head[u] = cnt;return ;
}
vector<int> G[N]; 
void tarjan(int x)
{low[x] = dfn[x] = ++ timestamp, stk.push(x), in_stk[x] = true;for (int i = head[x]; i != -1; i = e[i].nxt){int y = e[i].to;if(!dfn[y]){tarjan(y);low[x] = min(low[x], low[y]);}else if(in_stk[y]) low[x] = min(low[x], dfn[y]);}if(low[x] == dfn[x]){int y;scc_cnt ++;do{y = stk.top(), stk.pop();in_stk[y] = false;siz[scc_cnt]++, id[y] = scc_cnt;}while(y != x);}return ;
}
void build()
{for (int x = 1; x <= n; ++ x){for (int j = head[x]; j != -1; j = e[j].nxt){int y = e[j].to;if(id[x] != id[y]) G[id[x]].push_back(id[y]), in[id[y]] ++ ;}}return ;
}
int main()
{scanf("%d %d %d", &n, &m, &p);for (int i = 0; i <= n; ++ i) head[i] = -1;while(m --){int u, v;scanf("%d %d", &u, &v);add_edge(u, v);}for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);build();int ans = 0, sum = 0;for(int x = scc_cnt; x >= 1; -- x){if(!in[x]) g[x] = 1;for (int j = 0; j < G[x].size(); ++ j){int y = G[x][j];f[y] = max(f[y], f[x] + siz[x]);}}for (int x = scc_cnt; x >= 1; -- x){ans = max(ans, f[x] + siz[x]);for (int j = 0; j < G[x].size(); ++ j){int y = G[x][j];if(f[y] == f[x] + siz[x] && !vis[y]) vis[y] = 1, g[y] = (g[y] + g[x]) % p;}for (int j = 0; j < G[x].size(); ++ j) vis[G[x][j]] = 0;}for (int x = scc_cnt; x >= 1; -- x){if(ans == f[x] + siz[x]) sum = (sum + g[x]) % p;}printf("%d\n%d", ans, sum);return 0;
}

银河

这个题可以不用差分s约束里的spfa来判断,是因为这个图具有一定的特殊性,所有的边的权值都是正数->能用强连通分量保证稳定的线性时间复杂度。

问题就转化成了在拓扑图上面找 到一个点的最长路,然后累加就可以了。

/*
T = 1 A == B
T = 2 A < B
T = 3 A >= B
T = 4 A > B
T = 5 A <= B
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 300005;
typedef long long ll;
ll ans = 0;
struct edge1
{int to, nxt, w;
}e[M];
struct edge2
{int v, w;
};
vector<edge2> G[N];
int n, m, cnt = -1, timestamp = 0, scc_cnt = 0, head[N];  
int low[N], dfn[N], id[N], in[N], siz[N];
stack<int> stk;
bool in_stk[N], vis[N];
void add_edge(int u, int v, int w)
{e[++ cnt].to = v, e[cnt].nxt = head[u], e[cnt].w = w, head[u] = cnt;return ;
}
void tarjan(int x)
{low[x] = dfn[x] = ++timestamp, in_stk[x] = true, stk.push(x);for (int i = head[x]; i != -1; i = e[i].nxt){int y = e[i].to;if(!dfn[y]){tarjan(y);low[x] = min(low[x], low[y]);}else if(in_stk[y]) low[x] = min(low[x], dfn[y]);}if(low[x] == dfn[x]){int y;scc_cnt ++;do{y = stk.top(), stk.pop();id[y] = scc_cnt, siz[scc_cnt] ++;in_stk[y] = false;}while(y != x);}return ;
}
bool judge()
{bool pd = true;for (int x = 1; x <= n; ++ x){for (int j = head[x]; j != - 1; j = e[j].nxt){int y = e[j].to;if(id[x] == id[y] && e[j].w) {pd = false; break; }else if(id[x] != id[y]) G[id[x]].push_back((edge2){id[y], e[j].w}), in[id[y]] ++;}}return pd;
}
int dp[N];
int main()
{scanf("%d %d", &n, &m);for (int i = 0; i  <= n; ++ i) head[i] = -1;while(m --){int T, u, v;scanf("%d %d %d", &T, &u, &v);if(T == 1) add_edge(u, v, 0), add_edge(v, u, 0);else if(T == 2) add_edge(u, v, 1);else if(T == 3) add_edge(v, u, 0);else if(T == 4) add_edge(v, u, 1);else if(T == 5) add_edge(u, v, 0);}for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);if(judge() == false){printf("-1");}else{for (int i = 1; i <= scc_cnt; ++ i){if(!in[i]) G[scc_cnt + 1].push_back((edge2){i, 1});}for (int x = scc_cnt + 1; x >= 1; -- x){for (int j = 0; j < G[x].size(); ++ j){int y = G[x][j].v, w = G[x][j].w;dp[y] = max(dp[y], dp[x] + w);}}for (int x = scc_cnt; x >= 1; -- x) ans = ans + (ll)dp[x] * siz[x];printf("%lld", ans);}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MINE类型

1、什么是MIME类型MIME(Multipurpose Internet Mail Extensions)类型是在互联网上用于标识文件类型的标准方式。它在很多应用中得到了广泛的应用,例如电子邮件、Web浏览器和Web服务器等。MIME类型是由两部分组成的,第一部分是媒体类型(media type),用来表示文件的大类别…

解决微信小程序原生云开发退款报错“特约子商户商户号未授权服务商的产品权限”的问题

背景:微信小程序云开发支付没问题,退款时就会报这个错。 现象: 解决方法流程: 1、打开微信小程序开发者工具上面的云开发界面: 2、进入设置: 3、其他设置: 需要授权退款API权限,我这里已经授权了,未授权的话会有授权按钮,点击后会提示等待商户审核 4、我们来到微信支…

探索ChatGPT背后的前端黑科技

由于图片和格式解析问题,可前往 阅读原文在人工智能与互联网技术飞速发展的今天,像ChatGPT这样的智能对话系统已经成为科技领域的焦点。它不仅能够进行自然流畅的对话,还能以多种格式展示内容,为用户带来高效且丰富的交互体验。然而,这些令人惊叹的功能背后,离不开前端技…

OTA软件升级管理系统

OTA(空中下载技术)是通过空中下载的方式对车辆中的软件进行远程升级。经纬恒润OTA软件升级管理系统基于软件架构、应用架构、业务架构和技术架构,为整车提供云-管-端-屏完整OTA解决方案,可靠地完成系统更新、软件升级、功能迭代和安全漏洞修复等功能。同时支持固件升级(FOTA…

深度学习经典 - 鱼书 - 《深度学习入门:基于Python的理论与实现》 - PDF免费下载

深度学习经典“鱼书”,下载地址:https://pdfs.top/book/深度学习入门:基于Python的理论与实现.html。本书深入浅出地介绍了深度学习的原理,使用Python3从零开始构建深度学习模型。书中详细讲解了神经网络、误差反向传播法、卷积神经网络等核心技术,并探讨了深度学习在自动…

DeepSeek 相关知识学习和整理ing...

【硬件相关】 HBM3e:HBM3e是HBM(高带宽内存)技术的迭代升级版本,属于HBM3的扩展。它采用3D堆叠封装技术,将多个DRAM芯片垂直堆叠,通过硅互连通道传输数据,大幅提升带宽和容量,同时降低功耗。 迭代背景:HBM系列从第一代(HBM)到第五代(HBM3e)持续升级,每一代都提高…

打靶记录27——Tre

靶机: https://www.vulnhub.com/entry/tre-1,483/ 下载(镜像):https://download.vulnhub.com/tre/Tre.zip 难度:中目标:获得 Root 权限 + Flag攻击方法:主机发现 端口扫描 信息收集 进阶路径枚举 EXP 代码改造 突破边界方法1 突破边界方法2 突破边界方法3 权限提升主机发…

7. Docker 容器数据卷的使用(超详细的讲解说明)

7. Docker 容器数据卷的使用(超详细的讲解说明) @目录7. Docker 容器数据卷的使用(超详细的讲解说明)1. Docker容器数据卷概述2. Docker 容器数据卷的使用演示:2.1 宿主 和 容器之间映射添加容器卷2.2 容器数据卷 读写规则映射添加说明2.3 容器数据卷的继承和共享3. 最后:坑:…

到底值不值得本地部署残血版DeepSeek?一文说清!教你如何白嫖满血版DeepSeek

一、介绍最近一段时间,DeepSeek 备受关注,夏天也向身边朋友推荐。但它常无法使用,原因是受到大规模恶意攻击,且 IP 地址在美国。 通过以下网站可以查看DeepSeek网站状态: status.deepseek.com/ 可以发现,最近标红的就是故障中​若遇服务器繁忙提示,大概率是被攻击了,并…

干货:DeepSeek+SpringAI实现流式对话!

前面一篇文章我们实现了《炸裂:SpringAI内置DeepSeek啦!》,但是大模型的响应速度通常是很慢的,为了避免用户用户能够耐心等待输出的结果,我们通常会使用流式输出一点点将结果输出给用户。 那么问题来了,想要实现流式结果输出,后端和前端要如何配合?后端要使用什么技术实…

Ftrans文件安全外发系统,为企业数据保驾护航!

随着企业的不断发展,集团分公司及各部门需向外部客户、合作伙伴及海外同事外发文件。过去,主要通过邮件、FTP方式将数据进行外发,主要存在以下问题和挑战: 1.进行文件外发时需通过OA的审批,由于OA审批与FTP传输两个环节割裂,公司无法有效限制数据外发范围和管控数据外发安…

本地部署 DeepSeek:小白也能轻松搞定!

大家好,我是晓凡。 写在前面 最近DeepSeek太火了,以至于每个小伙伴都想试试。DeepSeek 的到来可谓是开启了全民AI热潮。 本以为DeepSeek本地化部署有多难,实际上验证后很简单,操作起来就像给电脑装个新软件那么简单,大约十多分钟可完成本地部署。 今天咱们来聊聊如何在自己…