萌新刚学Tarjan,啥也不会,肯定一堆错,请大佬指正谢谢

前置

强连通

  • 强连通:

在不是强连通图的有向图\(G\)内,其顶点\(u\),\(v\)两个方向上都存在有向路径,则\(u\)和\(v\)强连通

  • 强连通图:

对于有向图 \(G\) ,若\(G\)中任意两个结点连通,则称有向图\(G\)强连通。

  • 强连通分量:

有向图的极大强连通子图

对于 \(G\) 的极大强连通子图 \(S\),添加任意顶点都会导致 \(S\) 失去强连通的属性

无向图中删除一条边会使这个图的极大联通分量数增加,这条边就是桥

桥一定是 dfs搜索树 中的边,并且一个简单环中的边一定都不是桥

  • 判定法则:

对于时间戳\(dfn\)(在图的深度优先遍历中访问的顺序)

和追溯值\(low\)

(满足以下条件的节点的时间戳的最小值)

  • \(x\) 的子树节点
  • 通过不是搜索树的边可以到达 \(x\) 的子树节点的节点

满足\(\textit{dfn}[x]<\textit{low}[y]\)时,边 \((x,y)\) 是桥

一些变量

  • \(\textit{dfn}_u\) :深度优先搜索遍历时结点 \(u\) 被搜索的次序。

  • \(\textit{low}_u\) :在 \(u\) 的子树中能够回溯到的最早的已经在栈中的结点。设以 \(u\) 为根的子树为 \(\textit{Subtree}_u\) 。 \(\textit{low}_u\) 定义为以下结点的 \(\textit{dfn}\) 的最小值:

  1. \(\textit{Subtree}_u\) 中的结点;
  2. 从 \(\textit{Subtree}_u\) 通过一条不在搜索树上的边能到达的结点。

一个结点的子树内结点的 \(\textit{dfn}\) 都大于该结点的 \(\textit{dfn}\)。

从根开始的一条路径上的 \(\textit{dfn}\) 单调递增,\(\textit{low}\) 单调不下降。

DFS生成树

DFS生成树主要有\(4\)种边:

  1. 树边:黑色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边
  2. 反祖边:红色边(\(7 \rightarrow 1\) )指向祖先结点的边
  3. 横叉边:蓝色边(\(9 \rightarrow 7\))在搜索的时候遇到了一个已经访问过的结点,这个结点并不是当前结点的祖先
  4. 前向边:绿色边(\(3 \rightarrow 6\) )搜索的时候遇到子树中的结点的时候形成的

若结点\(u\)是某个强连通分量在搜索树中遇到的第一个结点(就是根),那么这个强连通分量的其余结点肯定是在DFS搜索树中以\(u\)为根的子树中。

正文

我们可以看出,桥一定是dfs搜索树上的边而且一定不是一个简单环内的任何一个边

只要通过桥的判定法则直接求就行

注意事项

\(1\). 因为遍历的是无向图,所以无论从哪里都能遍历到这个节点的父节点\(fa\),所以不能用\(fa\)来更新\(low[x]\)

\(2\). 如果存在重边\(dfn[fa]\)就可以用来更新\(low[s]\),

解决方法:记录递归进入每个节点的编号,把无向图的每一条边都看做两条边成对存储

若沿着编号 \(i\) 的边递归进入节点 \(x\) , 则忽略从 \(x\) 出发的编号为 \(i\) xor \(1\) 的边,通过别的边更新 \(low[x]\)

Tarjan算法直接求出一张无向图的所有的桥,注意不能用 \(x\) 的父节点 \(fa\) 更新 \(low[x]\)

代码

  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct LuoShuiTianYi{
  19. int ver,Next,edge,dfn,low;
  20. }t[maxm];
  21. int head[maxm],n,m,tot,num;
  22. bool bridge[maxm];
  23. void add(int x,int y/*,int z*/){
  24. t[++tot].ver=y;
  25. // t[tot].edge=z;
  26. t[tot].Next=head[x];
  27. head[x]=tot;
  28. }
  29. void tarjan(int x,int val){
  30. t[x].dfn=t[x].low=++num;
  31. for(int i=head[x];i;i=t[i].Next){
  32. int y=t[i].ver;
  33. if(!t[y].dfn){
  34. tarjan(y,i);
  35. t[x].low=min(t[x].low,t[y].low);
  36. if(t[y].low>t[x].dfn)
  37. bridge[i]=bridge[i^1]=1;
  38. }
  39. else if(i!=(val^1))
  40. t[x].low=min(t[x].low,t[y].dfn);
  41. }
  42. }
  43. signed main(){
  44. n=read(),m=read();
  45. tot=1;
  46. for(int i=1;i<=m;i++){
  47. int x=read(),y=read()/*,z=read()*/;
  48. add(x,y/*,z*/);
  49. add(y,x/*,z*/);
  50. }
  51. for(int i=1;i<=n;i++)
  52. if(!t[i].dfn)
  53. tarjan(i,0);
  54. for(int i=2;i<tot;i+=2)
  55. if(bridge[i])
  56. cout<<t[i^1].ver<<" "<<t[i].ver<<endl;
  57. }

割点

割点就是说删去节点 \(x\) 和所有和 \(x\) 关联的所有边后能够让无相图分裂成多个不相连的子图,节点 \(x\) 就是割点

割点判定定理:

若 \(x\) 不是搜索树的根节点,则当且仅当 \(x\) 的一个子节点 \(y\) 满足 \(dfn[x]\leq low[y]\)

若 \(x\) 是搜索树的根节点,则当且仅当搜索树上只要有两个节点 \(y_1,y_2\) 满足以上条件

代码

  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct LuoShuiTianYi{
  19. int ver,Next,edge,dfn,low;
  20. }t[maxm];
  21. int head[maxm],n,m,tot,num,root;
  22. bool cut[maxm];
  23. void add(int x,int y/*,int z*/){
  24. t[++tot].ver=y;
  25. // t[tot].edge=z;
  26. t[tot].Next=head[x];
  27. head[x]=tot;
  28. }
  29. void tarjan(int x){
  30. t[x].dfn=t[x].low=++num;
  31. int f=0;
  32. for(int i=head[x];i;i=t[i].Next){
  33. int y=t[i].ver;
  34. if(!t[y].dfn){
  35. tarjan(y);
  36. t[x].low=min(t[x].low,t[y].low);
  37. if(t[y].low>=t[x].dfn){
  38. f++;
  39. if(x!=root||f>1)
  40. cut[x]=1;
  41. }
  42. }
  43. else
  44. t[x].low=min(t[x].low,t[y].dfn);
  45. }
  46. }
  47. signed main(){
  48. n=read(),m=read();
  49. tot=1;
  50. for(int i=1;i<=m;i++){
  51. int x=read(),y=read()/*,z=read()*/;
  52. if(x==y) continue;
  53. add(x,y/*,z*/);
  54. add(y,x/*,z*/);
  55. }
  56. for(int i=1;i<=n;i++)
  57. if(!t[i].dfn)
  58. root=i,
  59. tarjan(i);
  60. for(int i=1;i<=n;i++)
  61. if(cut[i])
  62. cout<<i<<endl;
  63. }

双联通分量

介绍

如果一个无向图没有割点,那么称这个图为“点双连通图”,无向图的极大点双联通子图被称为「点双连通分量」,简称「v-DCC

如果一个无向图没有桥,那么称这个图为“边双连通图”,无向图的极大边双联通子图被称为「边双联通分量」,简称「e-DCC

两者合称「双联通分量」,简称「DCC」

定理

一张图是点双连通图,当且仅当满足两个条件之一

1.图的顶点数不超过\(2\)

2.图中任意两点都同时包含在至少一个简单环里

一张图是边双连通图,当且仅当任意一条边都包含在至少一个简单环里

边双连通分量

求法

先求出所有桥,然后全部删掉,分成的每个连通块都是一个边双联通分量

实现就是先用Tarjan标记所有的桥边,然后dfs划分出每个连通块

代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct LuoShuiTianYi{
  19. int ver,Next,edge,dfn,low;
  20. }t[maxm];
  21. int head[maxm],n,m,tot,num,c[maxm],dcc;
  22. bool bridge[maxm];
  23. void add(int x,int y/*,int z*/){
  24. t[++tot].ver=y;
  25. // t[tot].edge=z;
  26. t[tot].Next=head[x];
  27. head[x]=tot;
  28. }
  29. void tarjan(int x,int val){
  30. t[x].dfn=t[x].low=++num;
  31. for(int i=head[x];i;i=t[i].Next){
  32. int y=t[i].ver;
  33. if(!t[y].dfn){
  34. tarjan(y,i);
  35. t[x].low=min(t[x].low,t[y].low);
  36. if(t[y].low>t[x].dfn)
  37. bridge[i]=bridge[i^1]=1;
  38. }
  39. else if(i!=(val^1))
  40. t[x].low=min(t[x].low,t[y].dfn);
  41. }
  42. }
  43. void dfs(int x){
  44. c[x]=dcc;
  45. for(int i=head[x];i;i=t[i].Next){
  46. int y=t[i].ver;
  47. if(c[y]||bridge[i]) continue;
  48. dfs(y);
  49. }
  50. }
  51. signed main(){
  52. n=read(),m=read();
  53. tot=1;
  54. for(int i=1;i<=m;i++){
  55. int x=read(),y=read()/*,z=read()*/;
  56. add(x,y/*,z*/);
  57. add(y,x/*,z*/);
  58. }
  59. for(int i=1;i<=n;i++)
  60. if(!t[i].dfn)
  61. tarjan(i,0);
  62. for(int i=1;i<=n;i++)
  63. if(!c[i])
  64. dcc++,
  65. dfs(i);
  66. cout<<dcc<<endl;//e-DCC的个数
  67. for(int i=1;i<=n;i++)
  68. cout<<i<<" "<<c[i]<<endl;//输出i属于第几个连通块
  69. }
e-DCC的缩点
介绍

把每个e-DCC都看做一个节点,把桥边\((x,y)\)看做连接两个连通块,则会产生一棵树或一个森林,这就叫缩点

代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct HuaFengXiaYun{
  19. int ver,Next,edge;
  20. }tree[maxm];
  21. struct LuoShuiTianYi{
  22. int ver,Next,edge,dfn,low;
  23. }t[maxm];
  24. int head[maxm],headc[maxm],tc,n,m,tot,num,c[maxm],dcc;
  25. bool bridge[maxm];
  26. void add(int x,int y/*,int z*/){
  27. t[++tot].ver=y;
  28. // t[tot].edge=z;
  29. t[tot].Next=head[x];
  30. head[x]=tot;
  31. }
  32. void add_c(int x,int y){
  33. tree[++tc].ver=y;
  34. tree[tc].Next=headc[x];
  35. headc[x]=tc;
  36. }
  37. void tarjan(int x,int val){
  38. t[x].dfn=t[x].low=++num;
  39. for(int i=head[x];i;i=t[i].Next){
  40. int y=t[i].ver;
  41. if(!t[y].dfn){
  42. tarjan(y,i);
  43. t[x].low=min(t[x].low,t[y].low);
  44. if(t[y].low>t[x].dfn)
  45. bridge[i]=bridge[i^1]=1;
  46. }
  47. else if(i!=(val^1))
  48. t[x].low=min(t[x].low,t[y].dfn);
  49. }
  50. }
  51. void dfs(int x){
  52. c[x]=dcc;
  53. for(int i=head[x];i;i=t[i].Next){
  54. int y=t[i].ver;7
  55. if(c[y]||bridge[i]) continue;
  56. dfs(y);
  57. }
  58. }
  59. signed main(){
  60. n=read(),m=read();
  61. tot=1;
  62. for(int i=1;i<=m;i++){
  63. int x=read(),y=read()/*,z=read()*/;
  64. add(x,y/*,z*/);
  65. add(y,x/*,z*/);
  66. }
  67. for(int i=1;i<=n;i++)
  68. if(!t[i].dfn)
  69. tarjan(i,0);
  70. for(int i=1;i<=n;i++)
  71. if(!c[i])
  72. dcc++,
  73. dfs(i);
  74. tc=1;
  75. for(int i=2;i<=tot;i++){
  76. int x=t[i^1].ver,y=t[i].ver;
  77. if(c[x]==c[y]) continue;
  78. add_c(c[x],c[y]);
  79. }
  80. cout<<dcc<<" "<< tc/2<<endl;//森林
  81. for(int i=2;i<tc;i+=2)
  82. cout<<tree[i^1].ver<<" "<<tree[i].ver<<endl;
  83. }

点双联通分量

若一个节点没有其他边,那么这个节点自己构成一个v-DCC

除了孤立点,其他v-DCC的大小至少为2

并且一个割点不一定只属于一个v-DCC

求法
  1. 当一个节点第一次被访问就入栈
  2. 当割点判定法则成立时,无论 \(x\) 是否为根都要

    (1).一直弹出节点直到节点 \(y\) 被弹出

    (2).所有刚才弹出的节点和节点 \(x\) 构成一个v-DCC
代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct HuaFengXiaYun{
  19. int ver,Next,edge;
  20. }tree[maxm];
  21. struct LuoShuiTianYi{
  22. int ver,Next,edge,dfn,low;
  23. }t[maxm];
  24. int head[maxm],n,m,tot,num,root,sta[maxm],top,cnt,new_id[maxm],tc,headc[maxm];
  25. bool cut[maxm];
  26. vector<int>dcc[maxn];
  27. void add(int x,int y/*,int z*/){
  28. t[++tot].ver=y;
  29. // t[tot].edge=z;
  30. t[tot].Next=head[x];
  31. head[x]=tot;
  32. }
  33. void add_c(int x,int y){
  34. tree[++tc].ver=y;
  35. tree[tc].Next=headc[x];
  36. headc[x]=tc;
  37. }
  38. void tarjan(int x){
  39. t[x].dfn=t[x].low=++num;
  40. sta[++top]=x;
  41. if(x==root&&head[x]==0){
  42. dcc[++cnt].push_back(x);
  43. return;
  44. }
  45. int f=0;
  46. for(int i=head[x];i;i=t[i].Next){
  47. int y=t[i].ver;
  48. if(!t[y].dfn){
  49. tarjan(y);
  50. t[x].low=min(t[x].low,t[y].low);
  51. if(t[y].low>=t[x].dfn){
  52. f++;
  53. if(x!=root||f>1)
  54. cut[x]=1;
  55. cnt++;
  56. int z=0;
  57. while(z!=y){
  58. z=sta[top--];
  59. dcc[cnt].push_back(z);
  60. }
  61. dcc[cnt].push_back(x);
  62. }
  63. }
  64. else
  65. t[x].low=min(t[x].low,t[y].dfn);
  66. }
  67. }
  68. signed main(){
  69. freopen("1.in","r",stdin);
  70. n=read(),m=read();
  71. tot=1;
  72. for(int i=1;i<=m;i++){
  73. int x=read(),y=read()/*,z=read()*/;
  74. if(x==y) continue;
  75. add(x,y/*,z*/);
  76. add(y,x/*,z*/);
  77. }
  78. for(int i=1;i<=n;i++)
  79. if(!t[i].dfn)
  80. root=i,
  81. tarjan(i);
  82. num=cnt;
  83. for(int i=1;i<=n;i++){
  84. if(cut[i])
  85. new_id[i]=++num;
  86. }
  87. tc=1;
  88. for(int i=1;i<=cnt;i++){
  89. for(int j=0;j<dcc[i].size();j++){
  90. int x=dcc[i][j];
  91. if(cut[x]){
  92. add_c(i,new_id[x]);
  93. add_c(new_id[x],i);
  94. }
  95. }
  96. }
  97. for(int i=1;i<=cnt;i++){
  98. cout<<i<<" ";
  99. for(int j=0;j<dcc[i].size();j++)
  100. cout<<dcc[i][j]<<" ";
  101. puts("");
  102. }
  103. }
v-DCC的缩点
介绍

相比起e-DCC的缩点,v-DCC的缩点更复杂一些,因为一个割点可能属于多个v-DCC

若图中有 \(n\) 个割点和 \(m\) 个v-DCC,那么要建立一个有 \(n+m\) 个节点的新图

连边之后可以发现这个新的图是一棵树或者森林

代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct HuaFengXiaYun{
  19. int ver,Next,edge;
  20. }tree[maxm];
  21. struct LuoShuiTianYi{
  22. int ver,Next,edge,dfn,low;
  23. }t[maxm];
  24. int head[maxm],n,m,tot,num,root,sta[maxm],top,cnt,new_id[maxm],tc,headc[maxm];
  25. bool cut[maxm];
  26. vector<int>dcc[maxn];
  27. void add(int x,int y/*,int z*/){
  28. t[++tot].ver=y;
  29. // t[tot].edge=z;
  30. t[tot].Next=head[x];
  31. head[x]=tot;
  32. }
  33. void add_c(int x,int y){
  34. tree[++tc].ver=y;
  35. tree[tc].Next=headc[x];
  36. headc[x]=tc;
  37. }
  38. void tarjan(int x){
  39. t[x].dfn=t[x].low=++num;
  40. sta[++top]=x;
  41. if(x==root&&head[x]==0){
  42. dcc[++cnt].push_back(x);
  43. return;
  44. }
  45. int f=0;
  46. for(int i=head[x];i;i=t[i].Next){
  47. int y=t[i].ver;
  48. if(!t[y].dfn){
  49. tarjan(y);
  50. t[x].low=min(t[x].low,t[y].low);
  51. if(t[y].low>=t[x].dfn){
  52. f++;
  53. if(x!=root||f>1)
  54. cut[x]=1;
  55. cnt++;
  56. int z=0;
  57. while(z!=y){
  58. z=sta[top--];
  59. dcc[cnt].push_back(z);
  60. }
  61. dcc[cnt].push_back(x);
  62. }
  63. }
  64. else
  65. t[x].low=min(t[x].low,t[y].dfn);
  66. }
  67. }
  68. signed main(){
  69. freopen("1.in","r",stdin);
  70. n=read(),m=read();
  71. tot=1;
  72. for(int i=1;i<=m;i++){
  73. int x=read(),y=read()/*,z=read()*/;
  74. if(x==y) continue;
  75. add(x,y/*,z*/);
  76. add(y,x/*,z*/);
  77. }
  78. for(int i=1;i<=n;i++)
  79. if(!t[i].dfn)
  80. root=i,
  81. tarjan(i);
  82. num=cnt;
  83. for(int i=1;i<=n;i++){
  84. if(cut[i])
  85. new_id[i]=++num;
  86. }
  87. tc=1;
  88. for(int i=1;i<=cnt;i++){
  89. for(int j=0;j<dcc[i].size();j++){
  90. int x=dcc[i][j];
  91. if(cut[x]){
  92. add_c(i,new_id[x]);
  93. add_c(new_id[x],i);
  94. }
  95. }
  96. }
  97. cout<<num<<" "<<tc/2<<endl;//点数,边数
  98. for(int i=2;i<tc;i+=2)
  99. cout<<tree[i^1].ver<<" "<<tree[i].ver<<endl;
  100. }

强连通分量

简写SCC

追溯值:

这里的追溯值和无向图的不同,\(x\) 的追溯值 \(low[x]\) 定义为满足以下条件的节点的最小时间戳

  1. 该点在栈中
  2. 存在一条从\(x\)的子树出发的有向边,以该点为终点

算法

按照 dfs 序对所有结点进行搜索,维护每个点的 \(\textit{dfn}\) 与 \(\textit{low}\) ,让搜索到的结点入栈。每找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。搜索过程中,对于结点 \(u\) 和与其相邻的结点 \(v\)(\(v\) 不是 \(u\) 的父节点)进行分类讨论:

  • \(v\) 未被访问:继续对 \(v\) 进行dfs。在回溯过程中,用 \(\textit{low}_v\) 更新 \(\textit{low}_u\) 。因为存在从 \(u\) 到 \(v\) 的直接路径,所以 \(v\) 能够回溯到的已经在栈中的结点, \(u\) 也一定能够回溯到。

  • \(v\) 被访问过,在栈中:根据 low 值的定义,用 \(\textit{dfn}_v\) 更新 \(\textit{low}_u\) 。

  • \(v\) 被访问过,不在栈中:说明 \(v\) 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

判定法则

若\(x\)回溯前,\(low[x]=dfn[x]\)成立则从栈中从\(x\)到栈顶的所有节点构成一个SCC

代码

vector数组sccscc[i]记录编号为 \(i\) 的SCC中所有节点

  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct LuoShuiTianYi{
  19. int ver,Next,edge,dfn,low;
  20. }t[maxm];
  21. int head[maxm],n,m,tot,num,f,root,sta[maxm],top,cnt,ins[maxm];
  22. bool cut[maxm];
  23. vector<int>scc[maxm];
  24. void add(int x,int y/*,int z*/){
  25. t[++tot].ver=y;
  26. // t[tot].edge=z;
  27. t[tot].Next=head[x];
  28. head[x]=tot;
  29. }
  30. void tarjan(int x){
  31. t[x].dfn=t[x].low=++num;
  32. sta[++top]=x;
  33. ins[x]=1;
  34. for(int i=head[x];i;i=t[i].Next){
  35. if(!t[t[i].ver].dfn){
  36. tarjan(t[i].ver);
  37. t[x].low=min(t[x].low,t[t[i].ver].low);
  38. }
  39. else
  40. t[x].low=min(t[x].low,t[t[i].ver].dfn);
  41. }
  42. if(t[x].dfn==t[x].low){
  43. cnt++;
  44. int y;
  45. while(x!=y){
  46. y=sta[top--],ins[y]=0;
  47. c[y]=cnt,scc[cnt].push_back(y);
  48. }
  49. }
  50. }
  51. signed main(){
  52. n=read(),m=read();
  53. tot=1;
  54. for(int i=1;i<=m;i++){
  55. int x=read(),y=read()/*,z=read()*/;
  56. if(x==y) continue;
  57. add(x,y/*,z*/);
  58. }
  59. for(int i=1;i<=n;i++)
  60. if(!t[i].dfn)
  61. tarjan(i);
  62. for(int i=1;i<=cnt;i++){
  63. cout<<i<<" ";
  64. for(int j=0;j<=scc[i].size();j++)
  65. cout<<scc[i][j]<<" ";
  66. puts("");
  67. }
  68. }
缩点

类似无向图的e-DCC,每个SCC也可以缩成一个点

方法

对于每条有向边\((x,y)\),若\(\ c[x]\ 不等于\ c[y]\) 则在编号为\(c[x]\)和\(c[y]\)的SCC之间连边,最后可以得到一个有向无环图

代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct HuaFengXiaYun{
  19. int ver,Next,edge;
  20. }tree[maxm];
  21. struct LuoShuiTianYi{
  22. int ver,Next,edge,dfn,low;
  23. }t[maxm];
  24. int head[maxm],tc,headc[maxm],n,m,tot,num,f,root,sta[maxm],top,cnt,ins[maxm],c[maxm];
  25. bool cut[maxm];
  26. vector<int>scc[maxm];
  27. void add(int x,int y/*,int z*/){
  28. t[++tot].ver=y;
  29. // t[tot].edge=z;
  30. t[tot].Next=head[x];
  31. head[x]=tot;
  32. }
  33. void add_c(int x,int y){
  34. tree[++tc].ver=y;
  35. tree[tc].Next=headc[x];
  36. headx[x]=tc;
  37. }
  38. void tarjan(int x){
  39. t[x].dfn=t[x].low=++num;
  40. sta[++top]=x;
  41. ins[x]=1;
  42. for(int i=head[x];i;i=t[i].Next){
  43. if(!t[t[i].ver].dfn){
  44. tarjan(t[i].ver);
  45. t[x].low=min(t[x].low,t[t[i].ver].low);
  46. }
  47. else
  48. t[x].low=min(t[x].low,t[t[i].ver].dfn);
  49. }
  50. if(t[x].dfn==t[x].low){
  51. cnt++;
  52. int y;
  53. while(x!=y){
  54. y=sta[top--],ins[y]=0;
  55. c[y]=cnt,scc[cnt].push_back(y);
  56. }
  57. }
  58. }
  59. signed main(){
  60. n=read(),m=read();
  61. tot=1;
  62. for(int i=1;i<=m;i++){
  63. int x=read(),y=read()/*,z=read()*/;
  64. if(x==y) continue;
  65. add(x,y/*,z*/);
  66. }
  67. for(int i=1;i<=n;i++)
  68. if(!t[i].dfn)
  69. tarjan(i);
  70. for(int x=1;x<=n;x++){
  71. for(int i=head[x];i;i=t[i].Next){
  72. int y=t[i].ver;
  73. if(c[x]==c[y]) continue;
  74. add_c(c[x],c[y]);
  75. }
  76. }
  77. }

有向图的必经点和必经边

给定一张起点为\(S\),终点为\(T\)的有向图,若从\(S\)到\(T\)的每条路径一定会经过一个节点\(x\)那么这个点被称作必经点

同理,一定会经过一条边\((x,y)\)那么这条边就是必经边或桥

环上的点也可能是必经点,环上的边也可能是必经边,所以不能简单的缩点然后求解DAG

方法一 Lenguar-Tarjan算法

计算支配树然后在\(O(n\log n)\)时间内求解

那么什么是支配树呢?

给定一个有向图和一个起点\(S\),若去掉其中某个节点\(x\)无法到达终点\(P\),则称节点\(x\)支配点\(P\),\(x\)是\(p\)的一个支配点

支配点\(x\)可以有多个,这个集合称为\({X_p}\),最少有两个支配点,一个是起点,一个是终点

甚至支配点有传递的性质

a 是 b 的支配点,c 是 b 的支配点,那么必定存在 (a是b的支配点)|| (b是a的支配点)

这是一个有向图

说到底什么是支配树呢?

在支配\(p\)的点中,若一个支配点\(i≠p\),满足\(i\)被\(p\)剩下的所有支配点支配,则称\(i\)为\(p\)的最近支配点

把图的根节点记作\(root\),除了\(root\)以外的每一点都存在唯一的最近支配点,连上所有\((x的最近支配点,x)\)的边就能得到一颗树

这棵树被称作支配树

这是一颗支配树

那么怎么实现呢?

我们成功发现,支配树也太难了,建议使用方法2

方法二 (比较简单)

  1. 在原图中拓扑序进行DP求出路径条数\(f[x]\)
  2. 在反图中拓扑序进行DP求出路径条数\(dp[x]\)

其中\(f[T]\)为\(S\)到\(T\)的路径总条数

对于有向边\((x,y)\),若\(f[x]\times dp[y]=f[T]\)则\((x,y)\)为从\(S\)到\(T\)的必经边

对于点\(x\),若\(f[x]\times dp[x]=f[T]\)则\(x\)为从\(S\)到\(T\)的必经点

这个一般会超long long,可以对一个大质数取模再保存到\(f\)和\(dp\)数组里

然后可能会误判,只要不卡常可以多模几个质数再计算

代码

咕咕咕

2-SAT问题

例题

1. 受欢迎的牛

洛谷P不知道多少,普及组OJ

  • 题面

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

现在有\(N\)头牛,给你\(M\)对整数\((A,B)\),表示牛\(A\)认为牛\(B\)受欢迎。

这种关系是具有传递性的,如果\(A\)认为\(B\)受欢迎,\(B\)认为\(C\)受欢迎,那么牛\(A\)也认为牛\(C\)受欢迎。

你的任务是求出有多少头牛被所有的牛认为是受欢迎的。

  • 思路:

Tarjan缩点板子题稍微改动,思路就是先跑有向图的Tarjan,然后统计出度

缩点后找出度为 \(0\) 的点个数

如果个数\(>1\)则不存在受欢迎的牛(因为如果有出度那么对方就不喜欢自己,不然就缩成一个点了)

如果个数\(=0\)那么这个图不是DAG图,明显不可能这都缩完了啊怎么会

如果个数\(=1\)直接输出这个强连通分量里的节点数

  • 评价

不难,但是听说很经典

点击启动原神
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct HuaFengXiaYun{
  19. int ver,Next,edge;
  20. }tree[maxm];
  21. struct LuoShuiTianYi{
  22. int ver,Next,edge,dfn,low;
  23. }t[maxm];
  24. int head[maxm],out[maxm],tc,headc[maxm],n,m,tot,num,f,summ[maxm],sum,sta[maxm],top,cnt,ins[maxm],c[maxm],genshin[maxm];
  25. bool cut[maxm];
  26. vector<int>scc[0x66cc];
  27. void add(int x,int y/*,int z*/){
  28. t[++tot].ver=y;
  29. // t[tot].edge=z;
  30. t[tot].Next=head[x];
  31. head[x]=tot;
  32. }
  33. void add_c(int x,int y){
  34. tree[++tc].ver=y;
  35. tree[tc].Next=headc[x];
  36. headc[x]=tc;
  37. }
  38. void tarjan(int x){
  39. t[x].dfn=t[x].low=++num;
  40. sta[++top]=x;
  41. ins[x]=1;
  42. for(int i=head[x];i;i=t[i].Next){
  43. if(!t[t[i].ver].dfn){
  44. tarjan(t[i].ver);
  45. t[x].low=min(t[x].low,t[t[i].ver].low);
  46. }
  47. else
  48. t[x].low=min(t[x].low,t[t[i].ver].dfn);
  49. }
  50. if(t[x].dfn==t[x].low){
  51. cnt++;
  52. int y;
  53. while(x!=y){
  54. y=sta[top--],
  55. ins[y]=0;
  56. c[y]=cnt,
  57. scc[cnt].push_back(y);
  58. }
  59. }
  60. }
  61. signed main(){
  62. n=read(),m=read();
  63. tot=1;
  64. for(int i=1;i<=m;i++){
  65. int x=read(),y=read()/*,z=read()*/;
  66. if(x==y) continue;
  67. add(x,y/*,z*/);
  68. }
  69. for(int i=1;i<=n;i++)
  70. if(!t[i].dfn)
  71. tarjan(i);
  72. for(int x=1;x<=n;x++){
  73. for(int i=head[x];i;i=t[i].Next){
  74. int y=t[i].ver;
  75. if(c[x]==c[y]) continue;
  76. add_c(c[x],c[y]);
  77. sum++;
  78. }
  79. }
  80. for(int i=headc[1];i;i=tree[i].Next){
  81. summ[i]++;
  82. }
  83. int abc=0,ans=0;
  84. for(int i=1;i<=cnt;i++){
  85. if(summ[i]==0)
  86. abc++,
  87. ans+=scc[i].size();
  88. }
  89. cout<<ans;
  90. }

2.备用交换机

点击查看代码
  1. #include<bits/stdc++.h>
  2. #define lC q<<1
  3. #define rC q<<1|1
  4. #define int long long
  5. #define INF 0x66ccff0712
  6. #define endl "\n"
  7. #define maxm 0x66ccff
  8. #define maxn 0x6cf
  9. #define mid ((l+r)>>1)
  10. #define void inline void
  11. using namespace std;
  12. inline int read(){
  13. int s = 0,w = 1;char ch = getchar();
  14. while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}
  15. while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}
  16. return s*w;
  17. }
  18. struct LuoShuiTianYi{
  19. int ver,Next,edge,dfn,low;
  20. }t[maxm];
  21. int head[maxm],n,m,tot,num,root,sum;
  22. bool cut[maxm];
  23. void add(int x,int y/*,int z*/){
  24. t[++tot].ver=y;
  25. // t[tot].edge=z;
  26. t[tot].Next=head[x];
  27. head[x]=tot;
  28. }
  29. void tarjan(int x){
  30. t[x].dfn=t[x].low=++num;
  31. int f=0;
  32. for(int i=head[x];i;i=t[i].Next){
  33. int y=t[i].ver;
  34. if(!t[y].dfn){
  35. tarjan(y);
  36. t[x].low=min(t[x].low,t[y].low);
  37. if(t[y].low>=t[x].dfn){
  38. f++;
  39. if(x!=root||f>1)
  40. cut[x]=1;
  41. }
  42. }
  43. else
  44. t[x].low=min(t[x].low,t[y].dfn);
  45. }
  46. }
  47. signed main(){
  48. n=read();
  49. int x,y;
  50. tot=1;
  51. while(cin>>x>>y){
  52. if(x==y) continue;
  53. add(x,y);
  54. add(y,x);
  55. }
  56. for(int i=1;i<=n;i++)
  57. if(!t[i].dfn)
  58. root=i,
  59. tarjan(i);
  60. for(int i=1;i<=n;i++)
  61. if(cut[i]){
  62. sum++;
  63. }
  64. cout<<sum<<endl;
  65. for(int i=1;i<=n;i++)
  66. if(cut[i]){
  67. cout<<i<<endl;
  68. }
  69. }

完结撒花

Tarjan 学习笔记的更多相关文章

  1. [Tarjan 学习笔记](无向图)

    今天考试因为不会敲 Dcc 的板子导致没有AK(还不是你太菜了),所以特地写一篇博客记录 Tarjan 的各种算法 无向图的割点与桥 (各种定义跳过) 割边判定法则 无向边 (x,y) 是桥,当且仅当 ...

  2. Tarjan学习笔记

    \(Tarjan\)是个很神奇的算法. 给一张有向图,将其分解成强连通分量们. 强连通分量的定义:一个点集,使得里面的点两两可以互相到达,并且再加上另一个点都无法满足强连通性. \(Tarjan\)的 ...

  3. $tarjan$简要学习笔记

    $QwQ$因为$gql$的$tarjan$一直很差所以一直想着要写个学习笔记,,,咕了$inf$天之后终于还是写了嘻嘻. 首先说下几个重要数组的基本定义. $dfn$太简单了不说$QwQ$ 但是因为有 ...

  4. 仙人掌&圆方树学习笔记

    仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...

  5. OI知识点|NOIP考点|省选考点|教程与学习笔记合集

    点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...

  6. 算法学习笔记(5): 最近公共祖先(LCA)

    最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...

  7. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  8. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  9. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  10. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

随机推荐

  1. 深入理解Linux内核——内存管理(1)

    提要:本系列文章主要参考MIT 6.828课程以及两本书籍<深入理解Linux内核> <深入Linux内核架构>对Linux内核内容进行总结. 内存管理的实现覆盖了多个领域: ...

  2. 【故障公告】一而再,再而三,三翻四复:数据库服务器 CPU 100%

    会员救园,故障捣乱,每当困难时,故障们总是喜欢雪上加霜过来考验你. 今天下班前 17:43~17:47 期间,园子的 SQL Server 数据库服务器突然出现 CPU 100% 问题. 发现问题后, ...

  3. LVS DR模式负载均衡群集部署

    LVS DR模式负载均衡群集部署 1 LVS-DR 模式的特点 直接路由直接路由 调节器仅作为客户端的访问入口,节点服务器的响应消息是直接返回客户端的,不需要经过调节器(与NAT模式的区别)节点服务器 ...

  4. 在 Net7.0 环境下使用 RestSharp 发送 Http(FromBody和FromForm)请求

    一.简介 最近,在做一个数据传输的服务,我在一个Worker Service里面需要访问 WebAPI 接口,并传输数据,也可以提交数据.由于第一次使用 RestSharp 发送请求,也遇到了很多问题 ...

  5. stm32开发笔记

    STM32F103C8T6单片机简介 标准库与HAL库区别 寄存器 寄存器众多,需要经常翻阅芯片手册,费时费力: 更大灵活性,可以随心所欲达到自己的目的: 深入理解单片机的运行原理,知其然更知其所以然 ...

  6. 使用Eclipse生成CHM帮助文档(图解)

    使用Eclipse生成CHM帮助文档(图解) 博客分类: System Operate javadoc生成chm文档java生成api帮助文档api帮助文档生成工具 Eclipse JavaDoc和j ...

  7. MOOC慕课课表

    8. 教育法学,共11单元---课件全开放状态,可以1次全学完开课时间: 2020年08月17日 ~ 2020年12月16日进行至第1周,共18周学时安排: 3-5小时每周 9. 教师职业道德与教育政 ...

  8. 从原理到实战,详解XXE攻击

    本文分享自华为云社区<[安全攻防]深入浅出实战系列专题-XXE攻击>,作者: MDKing. 1 基本概念 XML基础:XML 指可扩展标记语言(Extensible Markup Lan ...

  9. CF1368B

    题目简化和分析: 因为要求长度最小,所以我们每个字符就应该发挥最大的价值,不会有没有作用的字符. 设有 \(x_1\) 个 \(c\) ,\(x_2\) 个 \(o\) ,\(x_3\) 个 \(d\ ...

  10. Unity - Win平台修改窗口标题

    原文参考 主要是通过user32.dll获取窗口句柄, 和Unity没有什么关系 using System; using System.Diagnostics; using System.Runtim ...