首页 > 其他分享 >暑假集训随笔4 强连通分量与点双、边双连通分量

暑假集训随笔4 强连通分量与点双、边双连通分量

时间:2023-08-17 16:24:30浏览次数:45  
标签:连通 dfs 边双 int tp low 分量

强连通分量

一个在有向图中的概念
\(强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。\)
\(强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图\)

tarjan算法的一些理解

注意到如果一些点属于一个强连通分量,那么从其中一个点一定可以“走到”所有的点,而对于dfs而言,“走到”的路径构成了dfs树,而那个depth最低的点就是dfs时这条路的起始点。但是如果想要形成一个闭环的话,光是走到是不够的,还需要一次重复的访问已经走到的点来构成这个闭环,而这次重复的访问对应的就是非树边。
然而有些点可能已经在之前的过程中参与构成了其他的强连通分量,这些点事实上应当被认为已经被删除,故可以用是否在栈中来判断它们是否被删除,因为作为一个之前访问过的点,除非被弹出否则它们必定在栈中。

int dfn[N], low[N], dfncnt, s[N], in_stack[N], tp;
int scc[N], sc;  // 结点 i 所在 SCC 的编号
int sz[N];       // 强连通 i 的大小

void tarjan(int u) {
  low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
  for (int i = h[u]; i; i = e[i].nex) {
    const int &v = e[i].t;
    if (!dfn[v]) {
      tarjan(v);
      low[u] = min(low[u], low[v]);
    } else if (in_stack[v]) {
      low[u] = min(low[u], dfn[v]);
    }
  }
  if (dfn[u] == low[u]) {
    ++sc;
    while (s[tp] != u) {
      scc[s[tp]] = sc;
      sz[sc]++;
      in_stack[s[tp]] = 0;
      --tp;
    }
    scc[s[tp]] = sc;
    sz[sc]++;
    in_stack[s[tp]] = 0;
    --tp;
  }
}

常见的用途

对于一些可能非DAG的图,可以考虑先用tarjan进行缩点再套\(topo\ sort\)进行求解。不过要注意强联通分量之间的重边需要被删除,可以考虑用map来处理

割点

在整颗搜索树上,如果u是根节点或者low[v]>=dfn[u]时这个点是割点。因此代码写起了与tarjan类似。但是有一个很关键的地方在于同一个u可能会多次被它的v判定为割点,因此要用一个数组去记录u有没有被记录为割点过,防止重复统计

点双连通分量

当一个图中没有割点时这个图就是点双

求解

对于割点u,将在u的子树中且在栈中的点认为是点双,逐次弹出,但是不弹出u,因为u可能属于多个点双

边双连通分量

注意到一个有趣(而且很显然)的事实:当指定了一个点不能访问其父亲(就是在dfs树上的father)时,我们事实上已经对这个无向图完成了定向,即转换成按照dfs树上的父子顺序由父亲指向儿子的有向图,而那些不在dfs树上的边则一定对应一条指向自己祖先的边,而非如同有向图一样还可能是指向一个访问过但是与当前节点没有祖先关系的点(即该边不可能是横向边)。
证明这一点很容易:由于我们的dfs树是伴随着dfs过程才被建立起来的,它事实上来自于我们原始的无向图,因此假设有一个点u有一条横向边指向v,那在dfs时v一定早就通过这条边的反向边或者间接访问的方式将u转化为了自己的子树中的某个节点,因此该边不不可能是"横向边"。
这个性质告诉我们一件很好的事情:求边联通分量的过程与有向图的强连通分量几乎相同,除了需要记录自己的上一个点/边,以及不必维护每个点是否在栈中的信息,因为每个被访问的点只有没有做tarjan或者在栈中两个可能这两件事。
另一个值得注意的事情是如果无向图有重边的话那么自己的father也是可能能被访问的,因此不能记录自己是从哪个点来的,而应该记录从哪条边来。
最后是洛谷模板题的代码

#include<bits/stdc++.h>
using namespace std;

const int maxn=600000+15;
int n,m,sum=1,tim,top;
int head[maxn],sd[maxn],dfn[maxn],low[maxn];
struct EDGE
{
    int to;int next;int from;
}edge[maxn*10];
void add(int x,int y)
{
    edge[++sum].next=head[x];
    edge[sum].from=x;
    edge[sum].to=y;
    head[x]=sum;
}
vector<int>ecc[maxn];
int cont_ecc;
void tarjan(int x,int pre)
{
	//cout<<x<<" "<<pre<<endl;
    low[x]=dfn[x]=++tim;
    stac[++top]=x;
    for (int i=head[x];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(i==(pre^1)){ //注意这里 异或运算的优先级低于比较运算
        //	cout<<i<<" "<<(pre^1);
        	continue;
		}
        
        if (!dfn[v]) {
        tarjan(v,i);
        low[x]=min(low[x],low[v]);
    }
        else 
        {
            low[x]=min(low[x],dfn[v]);
        }
    }
    if (dfn[x]==low[x])
    {
    	++cont_ecc;
    	
        int y;
        while (y=stac[top--])
        {
			ecc[cont_ecc].push_back(y);
            in[y]=0;
            if (x==y) break;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    sum=1;
    for (int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i,0);
	cout<<cont_ecc<<endl;
	for(int i=1;i<=cont_ecc;i++){
		cout<<ecc[i].size()<<" ";
		for(const auto &u:ecc[i]){
			cout<<u<<" ";
		}
		cout<<'\n';
	}

    return 0;
}

标签:连通,dfs,边双,int,tp,low,分量
From: https://www.cnblogs.com/WXk-k/p/17628017.html

相关文章

  • 强连通分量与tarjan算法
    强连通分量强连通:若一张有向图的节点两两之间可以互相抵达,那么这一张图是强连通的。强连通分量:极大的强连通子图。对图深度搜索的时候,每一个节点只访问一次,被访问过的节点与边构成搜索树。有向边按照访问的情况可以分为如下4类:1.树边:访问节点走过的边。2.返祖边:指向祖......
  • VTK 实例55:连通区域分析
    1#include<vtkAutoInit.h>2VTK_MODULE_INIT(vtkRenderingOpenGL2);3VTK_MODULE_INIT(vtkRenderingFreeType);4VTK_MODULE_INIT(vtkInteractionStyle);56#include<vtkSmartPointer.h>7#include<vtkSphereSource.h>8#include<v......
  • [刷题笔记] [JSOI2010] 连通数
    DescriptionProblem由于题目太短我直接上图罢Analysis题目描述非常简单,但是直接爆搜肯定会TLE,毕竟\(n\leq2000\)并且timelimit=300ms。我们发现如果题目保证无环直接topsort即可,问题就在环上,如何处理环呢?我们可以缩点,缩点笔记,显然我们只需要统计答案数,缩完点后就变成了......
  • 2023-08-08:给你一棵 n 个节点的树(连通无向无环的图) 节点编号从 0 到 n - 1 且恰好有 n
    2023-08-08:给你一棵n个节点的树(连通无向无环的图)节点编号从0到n-1且恰好有n-1条边给你一个长度为n下标从0开始的整数数组vals分别表示每个节点的值同时给你一个二维整数数组edges其中edges[i]=[ai,bi]表示节点ai和bi之间有一条无向边一条好路......
  • 2023-08-08:给你一棵 n 个节点的树(连通无向无环的图) 节点编号从 0 到 n - 1 且恰好有 n
    2023-08-08:给你一棵n个节点的树(连通无向无环的图)节点编号从0到n-1且恰好有n-1条边给你一个长度为n下标从0开始的整数数组vals分别表示每个节点的值同时给你一个二维整数数组edges其中edges[i]=[ai,bi]表示节点ai和bi之间有一条无向边一条好路径需要......
  • LeetCode 周赛上分之旅 # 37 多源 BFS 与连通性问题
    ⭐️本文已收录到AndroidFamily,技术和职场问题,请关注公众号[彭旭锐]和BaguTreePro知识星球提问。学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场LeetCode周赛的解题报告,一......
  • tarjan,点双和边双学习笔记。
    发现之前学的真的一塌糊涂呢(/ω\)很多非常精髓的地方理解的都不够好,比如说为啥我要用一棵dfs树来为框架,跑tarjan?这里我就理解的不好,所以我来重新写一篇,加深加深印象。以下一切默认为无向图。0.基本概念这里面说的非常不严谨,只是为了方便理解啦awa连通分量:你可以简单的......
  • 图论强联通分量(tarjan)算法
    图论强联通分量(tarjan)算法#include<bits/stdc++.h>usingnamespacestd;intn,m,cnt,cntb,ans;vector<int>edge[101];vector<int>belong[101];boolinstack[101];intdfn[101];intlow[101];stack<int>s;voidTarjan(intu){ ++cnt; dfn[u]=......
  • YUV文件-y,u,v分量验证
    通过程序采集yuv数据,并落1帧数据到文件中;一、此处记录下思路变化:1、第一步是了解YUV格式,为什么会比RGB节省空间;2、二则是按照YUV数据格式读取:因为没有任何消息头尾的封装,所以只需要看YUV是什么格式,再按照字节读取分量即可;3、验证总结:验证时,最好以图片进行验证,视频......
  • 算法训练 与1连通的点的个数
    主要思想是并查集,不懂的可以先了解下这个算法再来做题就明白了。c++实现:#include<iostream>#include<vector>usingnamespacestd;intf[10000];//找根节点intfind(intx){if(f[x]!=x)f[x]=find(f[x]);returnf[x];//不要加els......