强连通分量-tarjan算法缩点

 一. 什么是强连通分量?

强连通分量:在有向图G中,如果两个顶点u,v间(u->v)有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量。
简单点说就是:如果一个有向图中,存在一条回路,所有的结点至少被经过一次,这样的图为强连通图。
在强连图图的基础上加入一些点和路径,使得当前的图不在强连通,称原来的强连通的部分为强连通分量。

二. 强连通分量有什么用途呢?

在图论中,我们可以利用强连通分量进行缩点,从而可以减少很多不必要的操作,降低程序的时间复杂度。

三. tarjan求强连通分量

在讲tarjan算法之前我们先来讲几个概念:
1.树枝边:x是y的父节点,那么x到y的边称为树枝边。
2.前向边:x是y的祖先节点,那么x到y的边称为前向边。
3.后向边:y是x的的祖先节点,那么y到x的边称为后向边。
4.横叉边:x是y的祖先节点,并且y能到达已经搜过的点,那么这条边被称为横叉边。

根据定义可以得知:树枝边也是一个特殊的前向边。
我们在深度优先搜索的时候是从上向下搜的,所以画图看一下(自己画的图,可能有点丑 )
3951ea4ea6d74e628cfd978653bc9484.png#pic_center
知道这些定义后,我们来看一下tarjan算法是如何求强连通分量的。
在这里,我们引入一个时间戳的概念:在深度优先搜索的时候,当前结点x是第几个被搜索到的,记为df[x]。
对于每一个结点,我们定义两个时间戳。
df[x]:x结点是第几个被搜索到的。
low[x]:从x结点开始搜索,能搜索到深度最低的结点是什么,也就是能搜索到时间戳最小的结点。
在搜索的时候我们该如何判断是一个新的强连通分量呢?
如果一个图是强连通的话,那么必然会搜完他图中的所有结点,并且从任意一个结点到达另一个结点,所以强连通分量中的点都可以遍历到时间戳更小的点。
所以我们可以发现,如果一个结点的df[]==low[]的话,那么说明这个结点是不能搜到比自己的时间戳更小的点了。说明什么呢?说明该节点就是一个强连通分量的最高点,也就是一个新的强连通分量。

下面放入一个题(受欢迎的牛):

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

现在有 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≤10^4,
1≤M≤5×10^4
输入样例:
3 3
1 2
2 1
2 3
输出样例:
1
样例解释
只有第三头牛被除自己之外的所有牛认为是受欢迎的。

这题乍一看是传递闭包,第一时间会想到Floyd,但是看了数据范围之后发现传递闭包会超时,所以Floyd是不可行的。

我们用强连通分量来做。
题意:a->b且b->c,则a->c

要求的是有多少头牛被除自己之外的所有牛认为是受欢迎的(受欢迎的牛可以被其他的牛走到)。

总结:当一个强连通的出度为0,则该强连通分量中的所有点都被其他强连通分量的牛欢迎,但假如存在两及以上个出度=0的牛(强连通分量) 则必然有一头牛(强连通分量)不被所有牛欢迎。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1e4+10,M=5*N;

int h[N],e[M],ne[M],idx;  //邻接表
int id[N];   //id[x]:x结点属于第几个强连通分量
int cnt,times;  //cnt:强连通分量的个数,times:时间戳
int n,m;
bool st[N];  //st[x]:x结点是否在栈中
int df[N],low[N];  //df[x]:x结点第一次被搜索到的时间戳,low[x]:x结点能遍历到最高的点
int sizes[N];  //sizes[x]:第x强连通分量中点的个数
int stack[N],top;  //这里我们用数组模拟栈
int dout[N];  //dout[x]:第x个强连通分量的出度

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

void tarjan(int u){
    //当前点的时间戳
    df[u]=low[u]=++times;
    //把u点入栈
    stack[++top]=u;
    st[u]=true;
    //遍历u结点能到的点
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!df[j]){  //如果没有遍历过,那么就遍历它
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(st[j]) low[u]=min(low[u],low[j]);  //如果在栈中
    }
    
    //找到一个强连通分量
    if(df[u]==low[u]){
        cnt++;
        int t=stack[top--];
        id[t]=cnt;
        st[t]=false;
        sizes[cnt]++;
        while(t!=u){
            t=stack[top--];
            id[t]=cnt;
            st[t]=false;
            sizes[cnt]++;
        }
    }
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    
    for(int i=1;i<=n;i++){
        if(!df[i]){
            tarjan(i);
        }
    }
    
    for(int i=1;i<=n;i++){
        for(int j=h[i];j!=-1;j=ne[j]){
            int t=e[j];
            int a=id[i],b=id[t];  //a:i结点所在的强连通分量,b:t结点所在的强连通分量
            if(a!=b) dout[a]++;  //因为是a->b,所以a的出度++
        }
    }
    
    int zero=0;  //出度为0点的个数
    int sum=0;
    for(int i=1;i<=cnt;i++){
        if(!dout[i]){
            sum+=sizes[i];
            zero++;
        }
    }
    
    if(zero>1) printf("%d",0);
    else printf("%d",sum);
    
    return 0;
}

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

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

相关文章

NLP实战:调用Gensim库训练Word2Vec模型

目录 一、准备工作 1. 安装Gensim库 2. 对原始语料分词 二、训练Word2Vec模型 三、模型应用 1.计算词汇相似度 ​编辑 2. 找出不匹配的词汇 3. 计算词汇的词频 四、总结 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学…

Flask-RESTful的使用

Flask-RESTful的使用 Flask-RESTful基本使用安装定义资源Resources创建API实例添加资源到API运行Flask应用 请求处理请求解析参数校验 响应处理数据序列化定制返回格式 其他功能蓝图装饰器集合路由命名规范路由名称 Flask-RESTful Flask-RESTful是一个用于构建RESTful API的扩展…

C++类和对象 -- 知识点补充

补充 const成员函数static成员友元内部类匿名对象拷贝对象时的一些编译器优化 const成员函数 将const修饰的成员函数称为const成员函数&#xff0c;const修饰类成员函数&#xff0c;实际是修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的成员进行修改。…

使用MockJS进行前端开发中的数据模拟

在前端开发中&#xff0c;有时我们需要在没有后端接口的情况下进行前端页面的开发和测试。这时&#xff0c;我们可以使用MockJS来模拟数据&#xff0c;以便进行开发和调试。MockJS是一个用于生成随机数据和拦截Ajax请求的JavaScript库&#xff0c;它能够帮助我们快速搭建起一个…

Linux---用户切换命令(su命令、sudo命令、exit命令)

1. su命令 root用户拥有最大的系统操作权限&#xff0c;而普通用户在许多地方的权限是受限的。 普通用户的权限&#xff0c;一般在其HOME目录内是不受限的。 一旦出了HOME目录&#xff0c;大多数地方&#xff0c;普通用户仅有只读和执行权限&#xff0c;无修改权限。 su 是…

【操作系统】01.操作系统概论

操作系统的发展历史 未配置操作系统 手工操作阶段 用户独占全机&#xff0c;人机速度矛盾导致系统资源利用率低 脱机输入输出方式 为了缓解主机cpu和IO设备之间速度不匹配的矛盾&#xff0c;出现了脱机IO技术 在外围机的控制下&#xff0c;通过输入设备&#xff0c;将数据输…

耗时1周整理了网络安全学习路线,非常详细!

前言 这一期就出一个怎么学习网络安全的学习路线和方法&#xff0c;觉得有用的话三连收藏下 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习linux系统及命令的路上…

数论专题(3)逆元

目录 初步认识 逆元 定义 应用 费马小定理 好久没有更新我们的数论专题板块了&#xff0c;今天&#xff0c;我们就来探究一下新知——逆元。 初步认识 在数据非常大的情景下&#xff0c;我们通常会对数据先进行取模运算&#xff0c;来计算在一定的范围内进行处理。而运算…

Java 进阶 -- 集合(一)

本节描述Java集合框架。在这里&#xff0c;您将了解什么是集合&#xff0c;以及它们如何使您的工作更轻松&#xff0c;程序更好。您将了解组成Java Collections Framework的核心元素——接口、实现、聚合操作和算法。 介绍告诉您集合是什么&#xff0c;以及它们如何使您的工作…

day4,day5 -java集合框架

List、Set、Map等常用集合类的特点和用法。 常用集合类&#xff08;List、Set、Map 等&#xff09;是 Java 中提供的数据结构&#xff0c;用于存储和操作一组数据。以下是它们的特点和用法&#xff1a; List&#xff08;列表&#xff09;: 特点&#xff1a;有序集合&#xff0…

《深入理解计算机系统(CSAPP)》第8章 异常控制流 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

Android 12系统源码_WindowInsets (一)WindowInsets相关类和功能介绍

一、什么是WindowInsets? WindowInsets源码解释为Window Content的一系列插值集合,可以理解为可以将其理解为不同的窗口装饰区域类型,比如一个Activity相对于手机屏幕需要空出的地方以腾给StatusBar、Ime、NavigationBar等系统窗口,具体表现为该区域需要的上下左右的宽高。…

如何强制删除文件夹?这样操作就能搞定!

案例&#xff1a;我想删掉一些没有用的文件夹&#xff0c;释放一些电脑内存&#xff0c;但是我发现&#xff0c;有些文件夹并不能直接被删除。怎样才能删除这些文件夹&#xff1f;有没有小伙伴有解决的办法。 在使用电脑过程中&#xff0c;我们可能会遇到一些无法正常删除文件夹…

操作系统-进程和线程-进程和线程

目录 一、进程的概念、组成、特征 二、进程的状态与转换、组织 2.1进程状态 2.2进程转换关系 2.3进程的组织 链接方式 索引方式 三、进程控制 3.1进程的创建 3.2进程的终止 3.3进程的阻塞和唤醒 3.4进程的切换 ​编辑 四、进程通信 4.1共享存储 4.2消息传递 直接通信…

[中间件漏洞]nginx漏洞复现

目录 文件解析漏洞 原理分析 复现过程 防御方法 目录遍历漏洞 原理分析 复现过程 防御方法 空字节代码执行漏洞 复现过程 防御方法 整数溢出漏洞&#xff08;CVE-2017-7529&#xff09; 复现过程 防御方法 文件名逻辑漏洞&#xff08;CVE-2013-4547&#xff09; 复现过程 防…

南京市某高校计算机科学与技术专业性能测试与Loadrunner—考试试卷分析

XXX科技学院试卷 20 /20 学年 第 学期 课程所属部门&#xff1a; 课程名称&#xff1a; 课程编号&#xff1a; 考试方式&#xff1a;&#xff08;A、B、开、闭&#xff09;卷 使用班级&#xff1a; …

Android 12.0仿ios的hotseat效果修改hotseat样式

1.概述 最近在12.0产品项目需求的需要,系统原生Launcher的布局样式很一般,所以需要重新设计ui对布局样式做调整,产品在看到 ios的hotseat效果觉得特别美观,所以要仿ios一样不需要横屏铺满的效果 居中显示就行了,所以就要看hotseat的具体布局显示了 效果图如下: 2.仿io…

Python数据攻略-Pandas常用数据操作

大家好&#xff0c;我是Mr数据杨。今天我将带领各位走进Python的奇妙世界&#xff0c;就像步入三国演义那样热闹且复杂的战争年代。这里&#xff0c;数据就像那些智勇双全的武将和策士&#xff0c;我们要学习如何访问和修改它们&#xff0c;就如同诸葛亮那样掌控战局。 先来理…

springboot+vue医院网上预约挂号系统4n9w0

在线挂号平台已经成为它运营过程中至关重要的因素。医院挂号管理系统&#xff0c;是在计算机与通信设备十分完备的基础上&#xff0c;为医院管理人员、医生、用户提供的系统化的管理平台。 本系统需要实现基础的医院介绍、线上挂号、在线咨询、医生请假等几个主要功能。 管理员…

佛朗斯冲击港交所IPO:叉车租赁的未来是数字化?

佛朗斯“三战”IPO。 图源&#xff1a;佛朗斯 近日&#xff0c;广州佛朗斯股份有限公司&#xff08;下文简称为“佛朗斯”&#xff09;正式向港交所递交招股书&#xff0c;拟于港交所主板挂牌上市。 值得注意的是&#xff0c;这并不是佛朗斯首次冲击IPO。2019年6月和2020年7月…