当前位置: 首页 > news >正文

有向图的强连通分量

文章目录

  • 一些定义
  • 求强连通分量
  • 检查点是否在scc中
  • tarjam求scc
  • 缩点
    • 拓扑序深搜做法
  • 例题
    • 受欢迎的牛
      • 题意
      • 做法
      • 代码
    • 学校网络
      • 思路
      • debug
      • 代码
    • 最大半连通子图
      • 题意
      • 思想
      • 代码
    • 银河
      • 代码

一些定义

在这里插入图片描述
强连通分量:又叫极大连通分量,对于一个连通分量,如果在加上任何一个点之后,都不是连通分量了,就叫强连通分量

强连通分量作用:将任意一个有向图,通过缩点,转化成有向无环图(DAG),也就是拓扑图

缩点指的是将所有的连通分量缩成一个点

例如:
在这里插入图片描述
图中圈出来的部分就是一个强连通分量,缩点就是把中间的连通分量当成一个点,然后其他的单独的点也是一个连通分量,也就是上图缩完之后就只有五个点了
在这里插入图片描述
也就是这样一个有向无环图,好处就是在求最短路或者最长路的时候,可以按照拓扑序从前往后直接递推一遍就可以了,时间复杂度是O(n + m)的

求强连通分量

按照DFS的顺序来求

在这里插入图片描述
把所有的边分为四大类

第一类:树枝边:(x, y)要求x是y的父节点
第二类:前向边:(x, y)要求x是y的祖先节点,树枝边是一种特殊的前向边,也就是下图这样
在这里插入图片描述

第三类:后向边:(x, y),将上图中的x ->y的边方向反过来就可以
第四类:横叉边:(x, y),往我搜过的其他分支搜的时候,连向其他分支的一条边,被称为横叉边图中从x点指向最左边的那个点的边就是横叉边,但是右边那个不是,那个是树枝边
在这里插入图片描述
强连通分量简称scc

检查点是否在scc中

如果一个点在scc中,那么一定是它沿着后向边,走到了当前这个正在搜索的路径上,当前红色的就是递归的路径
在这里插入图片描述
情况1:存在后向边指向祖先节点
情况2:先走到横叉边,横叉边再往上走到祖先节点
对于任意的前向边,都不会构成回路,不需要管
所以存在scc一定是上面两种情况,当然,可能有自环

tarjam求scc

时间戳:搜索的时候,按照dfs的顺序给每个点一个编号
在这里插入图片描述
求强连通分量的时候,是求scc最上面的一个点,模板如下,求强连通分量,时间复杂度是线性的,每个点只会遍历一次,也就是O(n + m)

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp;//时间戳
    stk[ ++ top] = u, in_stk[u] = true;//加入栈中,这个栈里面存的是还没有遍历完的强连通分量
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top -- ];
            in_stk[y] = false;
            id[y] = scc_cnt;
            Size[scc_cnt] ++ ;
        } while (y != u);
    }
}

缩点

遍历一下所有边
在这里插入图片描述
注意,如果i和j是同一个连通分量中的话不要加边,加上的话,就相当于加上自环了,不是DAG了

注意!scc缩点之后,按照编号就已经是拓扑序了,不需要额外跑拓扑序

拓扑序深搜做法

拓扑序除了宽搜之外,还能用深搜来做
先传入一个u,然后遍历u的邻边,之后把u加入到序列之中,最后得到的序列一定是拓扑的逆序

例题

受欢迎的牛

受欢迎的牛

题意

给你一个图,让你看看有多少个点是其他点都能走到的

做法

只需要在反图上出发遍历所有的点,判断一下这个点是否能遍历到所有点就可以

但是,如果这个图是拓扑图的话,这个题就简单起来了,则只需要看是不是只有一个出度为0,如果存在超过两个,则说明至少有一个不被另一个喜欢,答案就是0

所以这个题缩点之后,变成DAG,就看出度为0的点有几个,如果只有一个的话,答案就是这个缩点的强连通分量中点的数量

对于这个题,不需要真的把图建出来,只需要遍历一下所有的边,统计一下点的出度就可以

代码

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define endl '\n'
#define int long long
#define fast ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 10010, M = 50010;
int n, m;
vector<int> v[N];
int dfn[N], low[N], ts;
int stk[N], top;
bool vis[N], in_stk[N]; 
int id[N], scc_cnt, sz[N];
int out[N];

void tarjan(int u)
{
    dfn[u] = low[u] = ++ ts;
    stk[++ top] = u, vis[u] = 1;
    for(auto i : v[u])
    {
        if(!dfn[i])
        {
            tarjan(i);
            low[u] = min(low[u], low[i]);
        }
        else if(vis[i]) low[u] = min(low[u], dfn[i]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do
        {
            y = stk[top --];
            vis[y] = 0;
            id[y] = scc_cnt;
            sz[scc_cnt] ++;
        }while(y != u);
    }
}

signed main()
{ 
    fast;
    cin >> n >> m;
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        v[a].push_back(b);
    }
    
    for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i);
    
    for (int i = 1; i <= n; i ++ )
    {
        for(auto j : v[i])
        {
            int a = id[i], b = id[j];//a表示i点所在连通分量,b表示j
            if(a != b) out[a] ++; //如果不在一个里面
        }
    }
    
    int zero = 0, sum = 0;
    for (int i = 1; i <= scc_cnt; i ++ ) 
        if(!out[i])
        {
            zero ++;
            sum += sz[i];
            if(zero > 1) 
            {
                sum = 0;
                break;
            }
        }
    
    cout << sum << endl;
    return 0;
}

学校网络

学校网络
就是我们需要把一个图变成强连通分量,最少需要加多少条边

思路

如果通过缩点把它变成一个有向无环图的话,是不是会变得比较简单呢?
缩完之后,假设一开始有p个入度为0的点,也就是p个起点,还有q个终点(出度为0),因此至少给p个点发信息,也就是第一问答案就是p,第二问结论是最少加max(p, q);证明自己去看视频

然后就,,就跟板子一样

debug

Y总传送debug小技巧,就是二分debug,从一半开始输出,慢慢缩小范围

代码

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define endl '\n'
#define int long long
#define fast ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 110;
int n, m;
vector<int> v[N];
int dfn[N], low[N], ts;
int stk[N], top;
bool vis[N], in_stk[N]; 
int id[N], scc_cnt, sz[N];
int out[N], in[N];

void tarjan(int u)
{
    dfn[u] = low[u] = ++ ts;
    stk[++ top] = u, vis[u] = 1;
    for(auto i : v[u])
    {
        if(!dfn[i])
        {
            tarjan(i);
            low[u] = min(low[u], low[i]);
        }
        else if(vis[i]) low[u] = min(low[u], dfn[i]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do
        {
            y = stk[top --];
            vis[y] = 0;
            id[y] = scc_cnt;
            sz[scc_cnt] ++;
        }while(y != u);
    }
}

signed main()
{ 
    fast;
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        int t;
        while (cin >> t, t) v[i].push_back(t);
        
    }
    
    for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i);
    
    for (int i = 1; i <= n; i ++ )
    {
        for(auto j : v[i])
        {
            int a = id[i], b = id[j];//a表示i点所在连通分量,b表示j
            if(a != b) //如果不在一个里面
            {
                out[a] ++; 
                in[b] ++;
            }
        }
    }
    
    int a = 0, b = 0;
    for (int i = 1; i <= scc_cnt; i ++ )
    {
        if (!in[i]) a ++ ;
        if (!out[i]) b ++ ;
    }

    cout << a << endl;
    if (scc_cnt == 1) cout << 0 << endl;
    else cout << max(a, b) << endl;

    return 0;
}

最大半连通子图

最大半连通子图

题意

就是求最大半连通子图

思想

在一个强连通分量当中,如果我们选择了强连通分量中的一部分点,还不如全部选上,因为这里面所有的点,都是可以相互到达的,强连通必然是半连通,所以缩点,得到DAG之后,就选一个链,这个一定满足要求,注意,这个链是不能分叉的,分叉之后不一定能满足可以到达的条件,因此,这个题就是要找一个最长的链。

可以用递推的方式来做,f[i]表示以第i个点为终点的最长链的节点数量之和是多少,也就是跑一个最长路,拓扑图上的最长路问题就是dp问题,就看一下所有i这个点的前驱,从前驱里面取一个最大值f[i]就可以,同时搞一个g[i]表示让f[i]取得最大值的方案数,这里就是一个很经典的统计最大值方案的问题,方程可以推出
在这里插入图片描述
在这里插入图片描述
注意:这里方案数的不同指的是最少有一个点是不同的,如果有一条边是不同的不算是不同的导出子图(根据定义可以得知,一个点如果被选中的话,与之相关的所有边都会被选中)

所以建图过程中对边直接去重就行,而且,我们计算方案数的时候是根据边来判的,因此,必须去重

总三步
1.tarjan
2.缩点建图,给重边去重
3.按照拓扑序递推

代码

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define x first
#define y second
#define PII pair<int, int>
#define ll long long
const int N = 100010;

int n, m, mod;
int id[N], scc_cnt, top, dfn[N], stk[N], ts, low[N], in_stk[N], vis[N];
int sz[N], f[N], g[N];
vector<int> v[N], vv[N];

void tarjan(int u)
{
    dfn[u] = low[u] = ++ ts;
    stk[++ top] = u, in_stk[u] = 1;
    for(auto i: v[u])
    {
        if(!dfn[i])
        {
            tarjan(i);
            low[u] = min(low[u], low[i]);
        }
        else if(in_stk[i]) low[u] = min(low[u], dfn[i]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do{
            y = stk[top --];
            in_stk[y] = 0;
            id[y] = scc_cnt;
            sz[scc_cnt] ++;
        }while(y != u);
    }
}

signed main()
{
    fast;
    cin >> n >> m >> mod;
    int a, b;
    while (m -- )
    {
        cin >> a >> b;
        v[a].push_back(b);
    }
    
    for (int i = 1; i <= n; i ++ ) if(!dfn[i]) tarjan(i);
    
    unordered_set<ll> s;
    
    for (int i = 1; i <= n; i ++ )
    {
        for(auto j : v[i])
        {
            a = id[i], b = id[j];
            int c = a * 1000000ll + b;
            if(a != b && !s.count(c))
            {
                vv[a].push_back(b);
                s.insert(c);
            }
        }
    }
    
    for(int i=scc_cnt; i>=1; i--)
    {
        if(!f[i])
        {
            f[i] = sz[i];
            g[i] = 1;
        }
        for(auto j  : vv[i])
        {
            if(f[j] < f[i] + sz[j])
            {
                f[j] = f[i] + sz[j];
                g[j] = g[i];
            }
            else if(f[j] == f[i] + sz[j]) g[j] = (g[j] + g[i]) % mod;
        }
    }
    
    int maxf = 0, sum = 0;
    
    for(int i=1; i<=scc_cnt; i ++)
        if(f[i] > maxf)
        {
            maxf = f[i];
            sum = g[i];
        }
        else if (f[i] == maxf) sum = (sum + g[i]) % mod;

    
    cout << maxf << endl;
    cout << sum << endl;
    return 0;
}

银河

银河
并不是所有的差分问题都可以用强连通分量解决,这个图比较特殊

步骤
1.tarjan
2.缩点建图
3.根据拓扑序递推

代码

相关文章:

  • 新建SpringBoot Maven项目中pom常用依赖配置及常用的依赖的介绍
  • 想搞清楚API网关到底是什么?请看这篇
  • 【STM32F4系列】【HAL库】电机控制(转速和角度)(PID实战1)
  • 设定目标(1)- 为什么你每天感觉很忙却没什么拿得出手的业绩?
  • Java 进阶集合Set、Map(二)
  • 2022-Docker常用命令
  • Spring中事务传播特性(Propagation)
  • Matlab:Matlab编程语言应用之数学统计(柱状图、曲线分析等)的使用方法简介、案例实现之详细攻略
  • YOLOv7改进之二十五:引入Swin Transformer
  • 03 nginx 是如何自动推导文件的 content-type 的
  • Java 8 Stream 从入门到进阶——像SQL一样玩转集合
  • C++STL详解(五)mapset的使用及其模拟实现
  • 串口通信-USART和UART的区别
  • Docker常见操作
  • YOLOV7详细解读(一)网络架构解读
  • ES6 ...操作符
  • jquery ajax学习笔记
  • Next.js之基础概念(二)
  • TypeScript实现数据结构(一)栈,队列,链表
  • zookeeper系列(七)实战分布式命名服务
  • 开源地图数据可视化库——mapnik
  • 看域名解析域名安全对SEO的影响
  • 面试总结JavaScript篇
  • 前端临床手札——文件上传
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 怎样选择前端框架
  • ​io --- 处理流的核心工具​
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #WEB前端(HTML属性)
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (java)关于Thread的挂起和恢复
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (六)Hibernate的二级缓存
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (数据结构)顺序表的定义
  • (一)kafka实战——kafka源码编译启动
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)Google的Objective-C编码规范
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .Net Core与存储过程(一)
  • .NET NPOI导出Excel详解
  • .net 程序发生了一个不可捕获的异常
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .Net7 环境安装配置
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • @EnableWebMvc介绍和使用详细demo
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节