2023-09-25 17:12阅读: 33评论: 3推荐: 3

tarjan学习笔记

tarjan学习笔记

为方便说明,x 指当前节点, y 指当前节点的子节点, fa 指当前节点的父亲节点。

0.前置知识

  • 搜索树

    在一张连通图中,所有的节点以及发生递归的边共同构成一棵搜索树。如果这张图不连通,则构成搜索森林。

    tu

    如图 从The_Shadow_Dragon博客上扣下来的

    • 搜索树上的边,称为 树边(绿色)。
    • 从祖先指向后代的非树边,称为 前向边(蓝色)。
    • 从后代指向祖先的非树边,称为 返祖边(黄色)。
    • 从子树中的节点指向另一子树节点的边,称为 横叉边(红色)。

    有向图的树边是单向的,无向图的树边是双向的(无向图中由儿子到父亲的边似乎不是树边?)。

  • dfn(时间戳)

    在对图的遍历中,各个顶点被遍历的顺序( dfn[i] 表示节点 i 第几次被遍历到)

  • low(追溯值)

    • 定义

      蓝书中给出的概念是:

      subtree(x) 表示搜索树中以 x 为根的子树。low[x]定义为以下节点的时间戳的最小值。

      1. subtree(x) 中的节点
      2. 通过1条不在搜索树上的边,能够到达 subtree(x) 的节点

      可以理解为,一个节点通过树边或一个非树边可以到达(回溯)的最先被遍历到的节点。( low[i] 表示在节点 i 可以到达的最小的 dfn 值)

      可以看出当当前节点有返祖边时,low将被更新为更小的值

    • low 的更新方式

      设 当前访问的节点为 x, x 能到达的点为 y

      • 初始化 dfn[x]=low[x]=++tot

      • y 未被遍历到,则该边为树边,递归访问 y , 因为 low 的性质,令 lowx=min(lowx,lowy)

      • y 被遍历过,则该边为返祖边或横叉边(存疑),因为 low 的性质,可以回到 x,令 lowx=min(lowx,dfny)

1. tarjan与有向图强连通分量

  • 相关定义

    • 强连通图

      在一个有向图中,若从任意一点可以到达其他所有点,则称之为强连通图

    • 强连通分量(SCC)

      一个有向图中的极大强连通子图(强连通图的强连通分量是它本身)

  • 强连通分量判定法则

    其实是求一个节点属于哪个强连通分量

    • 判断方式

      dfnx=lowx 可以理解为 节点 x 能返回到最早的节点就是 x 本身,则节点 xs.top() 内的所有节点为一个强连通分量。

    • 常用变量

      cnt:强连通图数量

      dfn[]:节点 i 第几次被遍历到

      low[]:在节点 i 存在的路径中,最小的 dfn

      belong[]:节点 i 属于第几个强连通图

      inStack[]:节点 i 是否在栈中

      tot:当前已有几个节点被访问

    • 代码实现

      inline void tarjan(int x){
        dfn[x]=low[x]=++tot;
        s.push(x);
        inStack[x]=1;
        for(int i = Head[x];i;i=Next[i]){
          int y=Ver[i];
          if(!dfn[y]){
            dfs(y);
            low[x]=min(low[x],low[y]);
          }
          else 
            if(inStack[y])low[x]=min(low[x],dfn[y]);
        }
        if(dfn[x]==low[x]){
          cnt++;
          while(s.top()!=x){
            belong[s.top()]=cnt;
            inStack[s.top()]=0;
            s.pop();
          }
          inStack[x]=0;
          belong[x]=cnt;
          s.pop();
      
        }
      }
      
  • 缩点

    因为一个节点只属于一个强连通分量,所以对于一些问题,我们可以将一个强连通分量看做一个点。

    即若存在有向边 x->y,若 belongxbelongy 则建立一条边 belong[x] -> belong[y]。

    • 代码实现

      for(int i = 1;i <= n;i++){
      	for(int j=A.Head[i];j;j=A.Next[j]){
      		int y=A.Ver[j];
      		if(belong[i]!=belong[y]){
      			A_.add(belong[i],belong[y]);
      			outp[belong[i]]++;
      		}
      	}
      }
      

    psA,A_

  • 拓展

    缩点后,图成为一个DAG(有向无环图)。对于某些题我们可以使用 DAGdp 或 SPFA 在DAG上求最长路。

    • DAGdp

      顾名思义是在有向无环图上的dp,用拓扑序作为求出dp顺序,需要在缩点事记录每个点的入度。(拓扑序需要根据点的入度求出)

      BFS

      • 代码实现
        inline void dagdp(){
        	for(int i = 1;i <= cnt;i++){
        		if(!inp[i]){
        			q.push(i);
        			f[i]=d[i];
        		}
        	}
        	while(!q.empty()){
        		int t=q.front();
        		q.pop();
        		for(int i = A_.Head[t];i;i=A_.Next[i]){
        			int y=A_.Ver[i];
        			inp[y]--;
        			f[y]=max(f[y],f[t]+d[y]);
        			if(!inp[y]) q.push(y);
        		}
        	}
        }
        
    • SPFA

      不想写了。

      dij 因为贪心的思想,所以跑不了最长路。

2. tarjan与无向图

  • 相关定义

    • 割点

      在无向图中,删去后使得连通分量数增加的结点称为割点。

    • 割边

      在无向图中,删去后使得连通分量数增加的边称为割边(桥)

    • 点双连通图

      不存在割点的无向连通图称为点双连通图。

    • 点双连通分量(V-BCC)

      一张图的极大点双连通子图称为点双连通分量。

    • 边双连通图

      不存在割边的无向连通图称为边双连通图。

    • 边双连通分量(E-BCC)

      一张图的极大边双连通子图称为边双连通分量。

  • tarjan求割边(割边判定法则)

    • 判断方式

      lowy>dfnx 可以理解为从 subtree(y) 出发,不经过 (x,y) ,不管走那条边,都不能到达 x 或比 x 更早访问的节点,最远只能到达 y(必经之边),故 (x,y) 一定是割边。

    • 有关父节点与重边

      因为无向边的缘故,节点 x 必定可以访问到它的父节点 fa 。因为 (x,fa) 为树边,跟据 low 的定义,不可以用 fa 来更新 x

      但若有重边( fax 有多个边相连),则只有一条 (x,fa) 算作树边。所以有重边时,能用 fa 来更新 x

    • 代码实现

      void tarjan(int x,int fa){
          dfn[x]=low[x]=++tot;
          bool fae=0;//是否已经有父亲跟当前节点相连
          for(int i = A.Head[x];i;i=A.Next[i]){
              int y=A.Ver[i];
              if(!dfn[y]){
                  tarjan(y,x);
                  low[x]=min(low[x],low[y]);
                  if(low[y]>dfn[x]){
                        bridge[i]=bridge[i^1]=1;
                  }
              }
              else{ 
                  if(y==fa && !fae) fae=1;
                  else low[x]=min(low[x],dfn[y]);
              } 
          }
      }
      
  • tarjan求割点(割点判定法则)

    • 判定为割点有两种情况

      1.此节点满足 lowydfnx 且为的搜索树的根节点并有两个及以上子树。

      2.此节点满足 lowydfnx 且不是搜索树的根节点。


      low[y]dfn[x] 可以理解为从 subtree(y) 出发,最远只能到达 x 而不能到达比 x 更早访问的节点(必经之点),故 x 一定是割点。

    • 有关父节点与重边(与割边相比较)

      蓝书上说:“因为割点判定法则是小于等于号,所以在求割点时,不必考虑父节点和重边问题”。

      我觉得比较抽象(反正我看不懂),所以我们不妨形象地解释一下:当此节点为割点时,无论有几条边与节点相连,删除此节点后都会断掉,所以有多少种边与此点相连都无所谓。

    • 代码实现

      void tarjan(int x,int root){
          dfn[x]=low[x]=++tot;
          int ch=0;
          for(int i = A.Head[x];i;i=A.Next[i]){
              int y=A.Ver[i];
              if(!dfn[y]){
                  tarjan(y,root);
                  low[x]=min(low[x],low[y]);
                  if(low[y]>=dfn[x]){
      				ch++;
                      if(x!=root||ch>=2){
                          cut[x]=1;
                      } 
                  }  
              }
              else low[x]=min(low[x],dfn[y]);
          }
      }
      

      if(x!=root||ch>=2) 只是判断根节点是否有两个根节点,非根节点可以全部通过。

  • 求边双连通分量(e-DCC)

    • 先用tarjan求出所有的割边 然后用dfs求出每一个点属于哪个边双连通分量。
    • 代码实现
      void dfs(int x){
          belong[x]=cnt;
          for(int i = A.Head[x];i;i=A.Next[i]){
              int y=A.Ver[i];
              if(!bridge[i]&&!belong[y]) dfs(y);
          }
      }
      
  • 求点双连通分量(v-DCC)

    一个可能属于多个 v-DCC

    • 流程:

      1. 当一个节点第一次被访问时,把该节点入栈。

      2. lowydfnx成立时,此时 x 是割点,subtree(y) 为一个点双连通分量。无论 x 是否为根,都要:

        (1)从栈中不断弹出节点,直至节点 y 被弹出。

        (2)刚才弹出的所有节点与割点 x 一起构成一个 v-DCC。

      解释:

      • 不弹栈直到 x 的原因:可能将其他子树的节点弹出。
      • 用do while 的原因:可能 subtree(y) 就一个节点( y 本身)
    • 代码实现

      void tarjan(int x,int root){
          dfn[x]=low[x]=++tot;
          int ch=0;
          s.push(x);
          if(x==root&&A.Head[x]==0){//孤立节点
              ans[++cnt].push_back(x);
              return;
          }
          for(int i=A.Head[x];i;i=A.Next[i]){
              int y=A.Ver[i];
              if(!dfn[y]){
                  tarjan(y,root);
                  low[x]=min(low[x],low[y]);
                  if(low[y]>=dfn[x]){
                      if(x!=root||ch>=2) cut[x]=1;
                      cnt++;
                      int t;
                      do{
                          t=s.top();
                          s.pop();
                          belong[t]=cnt;
                      }while(t!=y);
                      belong[x]=cnt;
                  }
              }
              else low[x]=min(low[x],dfn[y]);
          }
      }
      
posted @   tkt  阅读(33)  评论(3编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起