众所周知,tarjan是个非常nb的人,他发明了很多nb的算法,tarjan算法就是其中一个,它常用于求解强连通分量,割点和桥等。虽然具体实现的细节不太一样,但是大体思路是差不多的。先来说一下大体思路。


 强连通分量,缩点

我们先来定义几个东西

时间戳:在搜索树中被遍历到的次序

比如在下图中

每个节点按照遍历顺序编的号就是它的时间戳

dfn[i]:表示第i个点的时间戳

low[i]:表示点i及i的子树所能追溯到的最早的节点的时间戳

low数组看起来很难理解是不是?

先来看一张非常经典的图

我们发现对于结点1,3,2,4,它们的low值都是1。为什么呢?因为这些点都直接或者间接的能够追溯到的最早的点1,而点1的dfn值为1,所以这些点的low值自然也就是1了

我们可以通过手算发现图中有三个强连通分量:{1,2,3,4},{5},{6}

我们发现,每一个连通分量都有一个点(以下称为代表点)的low值=dfn值,也就是说这个点及它的子树所能到达的最早的点就是他自己。

于是可以知道,对于dfn=low的点就是这一个强连通分量的代表点

那么要求强连通分量,实际上就是求有多少个点的low=dfn

用一个栈来实现,寻找low时只在栈里面找,弹出时不断从栈顶弹出直到弹出这个点

代码:

  1. int dfn[],low[];
  2. //dfn表示时间戳
  3. //low表示点i及i的子树所能追溯到的最早的节点的时间戳
  4. int ind;
  5. //ind表示遍历顺序
  6. int in[],s[],top;
  7. //in表示当前这个点是否在队列中
  8. //s是模拟的栈
  9. //top是栈顶
  10. int cnt_scc;
  11. //强连通分量的个数
  12. int scc[],cntscc[];
  13. //scc表示每一个点属于哪一个强连通分量
  14. //cntscc表示强连通分量的大小
  15.  
  16. void tarjan(int x)
  17. {
  18. dfn[x]=++ind;
  19. low[x]=dfn[x];//初始化
  20. s[top++]=x;//入栈
  21. in[x]=;
  22. for(int i=head[x];i;i=edg[i].nxt)
  23. {
  24. int v=edg[i].to;
  25. if(!dfn[v])
  26. //如果是没有遍历到的树边就先对它进行操作
  27. {
  28. tarjan(v);
  29. low[x]=min(low[x],low[v]);//更新low值
  30. }
  31. else
  32. {
  33. if(in[v])//如果遍历过并且在栈中
  34. //为什么一定要在栈中?
  35. //因为如果不在栈中说明它已经属于其他强连通分量了
  36. //而每一次出栈都会弹出完整的强连通分量,所以这个点肯定不会产生影响
  37. {
  38. low[x]=min(low[x],dfn[v]);
  39. }
  40. }
  41. }
  42. if(dfn[x]==low[x])//如果找到强连通分量的代表点
  43. {
  44. cnt_scc++;
  45. while(s[top]!=x)//出栈
  46. {
  47. top--;
  48. in[s[top]]=;
  49. scc[s[top]]=cnt_scc;
  50. cntscc[cnt_scc]++;
  51. }
  52. }
  53. }

来看几道例题:

P2341 [HAOI2006]受欢迎的牛

如果有环,意味着这个环里的牛都互相喜欢

我们可以先求出环,然后把每一个环都看作一个点,这样整个图就变成了一个DAG(有向无环图)

看有几个点出度为0,如果大于一个点没有出边,就说明没有最受欢迎的牛,因为必定有一对牛相互不服

如果只有一个,那么强联通分量的大小就是答案

代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3.  
  4. int n,m;
  5.  
  6. int cnt,head[];
  7.  
  8. struct edge
  9. {
  10. int to,nxt;
  11. }edg[];
  12.  
  13. inline void add(int from,int to)
  14. {
  15. edg[++cnt].to=to;
  16. edg[cnt].nxt=head[from];
  17. head[from]=cnt;
  18. }
  19.  
  20. int dfn[],low[],ind,in[];
  21. int s[],top;
  22. int cnt_scc;
  23. int scc[],cntscc[];
  24.  
  25. void tarjan(int x)
  26. {
  27. dfn[x]=++ind;
  28. low[x]=dfn[x];
  29. s[top++]=x;
  30. in[x]=;
  31. for(int i=head[x];i;i=edg[i].nxt)
  32. {
  33. int v=edg[i].to;
  34. if(!dfn[v])
  35. {
  36. tarjan(v);
  37. low[x]=min(low[x],low[v]);
  38. }
  39. else
  40. {
  41. if(in[v])
  42. {
  43. low[x]=min(low[x],dfn[v]);
  44. }
  45. }
  46. }
  47. if(dfn[x]==low[x])
  48. {
  49. cnt_scc++;
  50. while(s[top]!=x)
  51. {
  52. top--;
  53. in[s[top]]=;
  54. scc[s[top]]=cnt_scc;
  55. cntscc[cnt_scc]++;
  56. }
  57. }
  58. }
  59.  
  60. int out[];
  61. int ans;
  62.  
  63. int main()
  64. {
  65. scanf("%d%d",&n,&m);
  66. for(int i=,x,y;i<=m;i++)
  67. {
  68. scanf("%d%d",&x,&y);
  69. add(x,y);
  70. }
  71. for(int i=;i<=n;i++)
  72. {
  73. if(!dfn[i]) tarjan(i);
  74. }
  75. for(int i=;i<=n;i++)
  76. {
  77. for(int j=head[i];j;j=edg[j].nxt)
  78. {
  79. int k=edg[j].to;
  80. if(scc[i]!=scc[k]) out[scc[i]]++;
  81. }
  82. }
  83. for(int i=;i<=cnt_scc;i++)
  84. {
  85. if(!out[i])
  86. {
  87. if(!ans)
  88. ans=i;
  89. else
  90. {
  91. cout<<;
  92. return ;
  93. }
  94. }
  95. }
  96. cout<<cntscc[ans];
  97. }

P2002 消息传递

我们发现如果这个题有环,那么不论在这个环上哪一个点开始传递信息,这个环中其他的点都可以到达,那么可以用tarjan把环缩成点。为了使每一个点都能被传递到,只需要找到所有入度为0的点,在这些点上开始传递信息就好了

代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3.  
  4. int n,m;
  5.  
  6. int head[],cnt;
  7. struct edge
  8. {
  9. int to,nxt;
  10. }edg[];
  11.  
  12. inline void add(int from,int to)
  13. {
  14. edg[++cnt].to=to;
  15. edg[cnt].nxt=head[from];
  16. head[from]=cnt;
  17. }
  18.  
  19. int low[],dfn[],ind;
  20. int s[],top;
  21. bool in[];
  22. int scc[],cnt_scc;
  23.  
  24. inline void tarjan(int x)
  25. {
  26. dfn[x]=++ind;
  27. low[x]=dfn[x];
  28. in[x]=;
  29. s[top++]=x;
  30. for(int i=head[x];i;i=edg[i].nxt)
  31. {
  32. int v=edg[i].to;
  33. if(!dfn[v])
  34. {
  35. tarjan(v);
  36. low[x]=min(low[x],low[v]);
  37. }
  38. else
  39. {
  40. if(in[v])
  41. low[x]=min(low[x],dfn[v]);
  42. }
  43. }
  44. if(low[x]==dfn[x])
  45. {
  46. cnt_scc++;
  47. while(s[top]!=x)
  48. {
  49. in[s[--top]]=;
  50. scc[s[top]]=cnt_scc;
  51. }
  52. }
  53. }
  54.  
  55. int ans;
  56. int gin[];
  57.  
  58. int main()
  59. {
  60. cin>>n>>m;
  61. for(int i=;i<=m;i++)
  62. {
  63. int x,y;
  64. scanf("%d%d",&x,&y);
  65. if(x!=y)
  66. add(x,y);
  67. }
  68. for(int i=;i<=n;i++)
  69. {
  70. if(!dfn[i]) tarjan(i);
  71. }
  72. for(int i=;i<=n;i++)
  73. {
  74. for(int j=head[i];j;j=edg[j].nxt)
  75. {
  76. int v=edg[j].to;
  77. if(scc[v]!=scc[i])
  78. {
  79. gin[scc[v]]++;
  80. }
  81. }
  82. }
  83. for(int i=;i<=cnt_scc;i++)
  84. {
  85. if(!gin[i]) ans++;
  86. }
  87. cout<<ans;
  88. }

类似的题还有洛谷1262,这里就先不说了


tarjan求割点

什么是割点?

给你一张连通图,在上面找一个点,如果去掉这个点和所有连着它的边,整个图就不能保持连通,那么这个点就是割点

比如这张图,里面的割点有1,4,5

怎么求割点?

首先选定一个dfs树的树根,从这个点开始遍历整张图。

对于根节点,判断是不是割点显然只需要看他的子树的个数是不是大于等于2

对于非根节点x,如果存在儿子节点y,使得dfn[x]<=low[y],则x一定是割点。

显然如果x的所有儿子能够不经过x直接到达他的祖先,这个点就一定不是割点;反之,则说明去掉它一定会改变图的连通性

代码:

  1. int low[],dfn[],ind,ans;
  2. bool cut[];
  3.  
  4. inline void tarjan(int x,int fa)
  5. {
  6. dfn[x]=++ind;
  7. low[x]=dfn[x];
  8. int ch=;
  9. for(int i=head[x];i;i=edg[i].nxt)
  10. {
  11. int v=edg[i].to;
  12. if(!dfn[v])
  13. {
  14. tarjan(v,fa);
  15. low[x]=min(low[x],low[v]);
  16. if(low[v]>=dfn[x]&&x!=fa) cut[x]=;
  17. if(x==fa) ch++;
  18. }
  19. else
  20. {
  21. low[x]=min(low[x],dfn[v]);
  22. }
  23. }
  24. if(x==fa&&ch>=) cut[fa]=;
  25. }

例:

P3388 【模板】割点(割顶)

代码:
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3.  
  4. int n,m;
  5.  
  6. int head[],cnt;
  7. struct edge
  8. {
  9. int to,nxt;
  10. }edg[];
  11.  
  12. inline void add(int from,int to)
  13. {
  14. edg[++cnt].to=to;
  15. edg[cnt].nxt=head[from];
  16. head[from]=cnt;
  17. }
  18.  
  19. int low[],dfn[],ind,ans;
  20. bool cut[];
  21.  
  22. inline void tarjan(int x,int fa)
  23. {
  24. dfn[x]=++ind;
  25. low[x]=dfn[x];
  26. int ch=;
  27. for(int i=head[x];i;i=edg[i].nxt)
  28. {
  29. int v=edg[i].to;
  30. if(!dfn[v])
  31. {
  32. tarjan(v,fa);
  33. low[x]=min(low[x],low[v]);
  34. if(low[v]>=dfn[x]&&x!=fa) cut[x]=;
  35. if(x==fa) ch++;
  36. }
  37. else
  38. {
  39. low[x]=min(low[x],dfn[v]);
  40. }
  41. }
  42. if(x==fa&&ch>=) cut[fa]=;
  43. }
  44.  
  45. int main()
  46. {
  47. cin>>n>>m;
  48. for(int i=;i<=m;i++)
  49. {
  50. int x,y;
  51. scanf("%d%d",&x,&y);
  52. add(x,y);
  53. add(y,x);
  54. }
  55. for(int i=;i<=n;i++)
  56. {
  57. if(!dfn[i]) tarjan(i,i);
  58. }
  59. for(int i=;i<=n;i++)
  60. {
  61. if(cut[i]==) ans++;
  62. }
  63. cout<<ans<<endl;
  64. for(int i=;i<=n;i++)
  65. {
  66. if(cut[i])
  67. printf("%d ",i);
  68. }
  69. }
 

Tarjan算法整理的更多相关文章

  1. Tarjan 算法 自学整理

    算法介绍 如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量( ...

  2. 求图的强连通分量--tarjan算法

    一:tarjan算法详解 ◦思想: ◦ ◦做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间 ...

  3. 割点(Tarjan算法)【转载】

    本文转自:www.cnblogs.com/collectionne/p/6847240.html 供大家学习 前言:之前翻译过一篇英文的关于割点的文章(英文原文.翻译),但是自己还有一些不明白的地方, ...

  4. Tarjan算法及其应用

    Tarjan算法及其应用 引入 tarjan算法可以在图上求解LCA,强连通分量,双联通分量(点双,边双),割点,割边,等各种问题. 这里简单整理一下tarjan算法的几个应用. LCA http:/ ...

  5. 割点(Tarjan算法)

    本文可转载,转载请注明出处:www.cnblogs.com/collectionne/p/6847240.html .本文未完,如果不在博客园(cnblogs)发现此文章,请访问以上链接查看最新文章. ...

  6. Tarjan算法分解强连通分量(附详细参考文章)

    Tarjan算法分解强连通分量 算法思路: 算法通过dfs遍历整个连通分量,并在遍历过程中给每个点打上两个记号:一个是时间戳,即首次访问到节点i的时刻,另一个是节点u的某一个祖先被访问的最早时刻. 时 ...

  7. 20行代码实现,使用Tarjan算法求解强连通分量

    今天是算法数据结构专题的第36篇文章,我们一起来继续聊聊强连通分量分解的算法. 在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实 ...

  8. 算法学习笔记:Tarjan算法

    在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实现.今天介绍的算法名叫Tarjan,同样是一个很奇怪的名字,奇怪就对了,这也是以 ...

  9. 浅谈 Tarjan 算法之强连通分量(危

    引子 果然老师们都只看标签拉题... 2020.8.19新初二的题集中出现了一道题目(现已除名),叫做Running In The Sky. OJ上叫绮丽的天空 发现需要处理环,然后通过一些神奇的渠道 ...

随机推荐

  1. idea配置less自动编译

    参考: idea配置less自动编译 1. 电脑安装node.js环境: window下直接上官网下载node.msi文件下载安装即可 安装完成后在命令行执行如下命令表明安装成功 npm -v nod ...

  2. [译]送给 ES6 开发者的7个 hack

    关注原来的 JavaScript hacks,上面有一些新的好东西.2018 使用 JavaScript 写代码真的又变得有意思了! Hack #1 — 交换变量 使用数组结构来交换值 let a = ...

  3. fpga配置方式 .jic固化为ps模式

    FPGA不同下载方式的区别[扫盲]以及如何利用AS模式固化程序(转载)     主动配置方式(AS)和被动配置方式(PS)和最常用的(JTAG)配置方式: AS由FPGA器件引导配置操作过程,它控制着 ...

  4. 配置阿里云SLB全站HTTPS集群(以下内容仅为流程,信息可能有些对应不上)

    1)登录阿里云购买两台实例 1.1) 按量付费购买两台实例 1.2) 配置网络可以不选择分配外网 1.3) 自定义密码 1.4) 购买完成 1.5) 实例列表 2)购买SLB实例 2.1)按量付费购买 ...

  5. 【转】uboot中的mmc命令

    转自:https://www.cnblogs.com/yxwkf/p/3855383.html 1:mmcinfo 输入: mmcinfo 显示结果:Manufacturer ID: 45OEM: 1 ...

  6. GUI学习之二十三——QComboBox学习总结

    我们在前面分别介绍了两种输入控件:纯键盘文本输入和步长调节器,下面我们来学习下组合框(下拉选择输入). 一.简介 1.下拉框是一个组合控件(包含一个文本显示控件和一个按钮).它默认显示最小的控件给用户 ...

  7. 高并发-原子性-AtomicInteger

    线程不安全: //请求总次数private static int totalCount = 10000;//最大并发数private static int totalCurrency = 100;// ...

  8. mysql merge引擎分表

    ---------------------创建表一--------------------------------------DROP TABLE a1;CREATE TABLE `a1` ( `id ...

  9. Node.js企业开发:应用场景

    要想用Node.js首先需要知道它到底是什么, 有哪些优缺点. 然后我们才能知道到底 Node.js 适合哪些应用场景. Node.js 维基百科:“Node.js 是谷歌 V8 引擎.libuv平台 ...

  10. Java 内存结构之虚拟机栈

    2.虚拟机栈 定义:虚拟机栈(Java Virtual Machine Stacks)就是每个线程运行需要的内存空间,栈由一个一个的栈帧(Frame)组成,栈帧就是每个方法运行时需要的内存(方法的参数 ...