首页 > 其他分享 >【学习笔记】Tarjan

【学习笔记】Tarjan

时间:2023-07-12 18:45:05浏览次数:45  
标签:缩点 Tarjan 连通 笔记 学习 dfn low 节点 分量

前言:

凡事都得靠自己 --bobo

  • 催隔壁 K8He n 天了让他写Tarjan的学习笔记,但貌似还没有动静,所以决定自己写一个。

正文

  • 本文配套题单:14.图论-tarjan(强连通分量、割点、割边)

  • 前置知识

    1. 熟练使用链式前向星
    2. 强连通、强连通图、强连通分量的定义(详见oi-wiki,这里不再赘述)
    3. 如图(从学校课件上扒下来的图),
    • \({\color{green}树边}\): \(DFS\) 时经过的点,即 \(DFS\) 搜索树上的边。
    • \({\color{yellow}返祖边}\):也叫回边,与 \(DFS\) 方向相反,从某个结点指向其某个祖先的边。
    • \({\color{red}横叉边}\): 从某个结点指向搜索树中另一子树终端某结点的边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点并不是当前结点的祖先时形成的。
    • \({\color{blue}前向边}\): 指向子树中节点的边,父节点指向子孙结点。
      • PS:前向边无用,因为通过树边就能直接到达这个点。
    • 返祖边与树边必定构成环,横叉边可能与树边构成环。
    • 无向图不存在横叉边。
    • 如果节点 \(x\) 是某个强连通分量在搜索树中遇到的第一个节点,那么这个强连通分量的其余结点肯定在搜索树中以 \(x\) 为根的子树中,节点 \(x\) 被称为这个强连通分量的根。

    3.时间戳、追溯值

    • 时间戳 \(dfn\) :用来标记某个节点在进 \(DFS\) 时被访问的时间顺序(由小到大)。
    • 追溯值 \(low\) :用来表示从当前节点 \(x\) 作为搜索树的根节点出发,能够访问到的所有节点中,时间戳 (\(dfn\))最小的值。
    • 当 \(dfn[x]==low[x]\) 时,以 \(x\) 为根的搜索子树中所有节点是一个强连通(从栈顶到 \(x\) 的所有节点)分量。
    • \(low[x]\) 的计算方法
      • 如果 \(x->y\) 是树边(没有被访问过),那么 \(low[x]=min(low[x],low[y])\) 。
      • 如果 \(x->y\) 是返祖边(访问过,且在栈中),那么 \(low[x]=min(low[x],dfn[y])\) 。
      • 如果 \(x->y\) 是横叉边(不在栈中),那么 什么都不做。
      for(i=head[x];i!=0;i=e[i].next)
      {
          if(dfn[e[i].to]==0)
          {
              tarjan(e[i].to);
              low[x]=min(low[x],low[e[i].to]);
          }
          else
          {
              if(ins[e[i].to]==1)//ins[x]表示x是否在栈内
              {
                  low[x]=min(low[x],dfn[e[i].to]);
              }
          }
      }
      

    4.时间复杂度 \(O(N+M)\)

    • 运行Tarjan算法的过程中,每个节点都没访问了一次,且只进出了一次栈,每条边也只被访问了一次,故时间复杂度为 \(O(N+M)\) 。
  • 强连通分量(Strongly Connected Components,SCC)

    luogu B3609 [图论与代数结构 701] 强连通分量

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long 
    #define endl '\n'
    struct node
    {
        int next,to;
    }e[100001];
    vector<int>scc[100001];
    stack<int>s;
    int head[100001],dfn[100001],low[100001],ins[100001],vis[100001],c[100001],cnt=0,tot=0,ans=0;
    void add(int u,int v)
    {
        cnt++;
        e[cnt].next=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void tarjan(int x)
    {
        int i,k=0;
        tot++;
        dfn[x]=low[x]=tot;
        ins[x]=1;
        s.push(x);
        for(i=head[x];i!=0;i=e[i].next)
        {
            if(dfn[e[i].to]==0)
            {
                tarjan(e[i].to);
                low[x]=min(low[x],low[e[i].to]);
            }
            else
            {
                if(ins[e[i].to]==1)//说明e[i].to是祖先节点or左子树节点
                {
                    low[x]=min(low[x],dfn[e[i].to]);
                }
            }
        }
        if(dfn[x]==low[x])//如果这里构成了一个强连通分量
        {
            ans++;//ans是强连通分量的编号
            while(x!=k)//将这个强连通分量内的点标记一下
            {
                k=s.top();
                ins[k]=0;
                c[k]=ans;//c[k]表示k属于哪个强连通分量内
                scc[ans].push_back(k);
                s.pop();
            }
        }
    }
    int main()
    {
        int n,m,i,j,u,v;
        cin>>n>>m;
        for(i=1;i<=m;i++)
        {
            cin>>u>>v;
            add(u,v);
        }
        for(i=1;i<=n;i++)
        {
            if(dfn[i]==0)
            {
                tarjan(i);
            }
        }
        cout<<ans<<endl;
        for(i=1;i<=n;i++)
        {
            if(vis[c[i]]==0)
            {
                vis[c[i]]=1;
                sort(scc[c[i]].begin(),scc[c[i]].end());
                for(j=0;j<scc[c[i]].size();j++)
                {
                    cout<<scc[c[i]][j]<<" ";
                }
                cout<<endl;
            }
        }
        return 0;
    }
    

    luogu P2661 [NOIP2015 提高组] 信息传递

    • 易知本题答案为最小的强连通分量大小(非1)
      scc=0;
      while(x!=k)
      {
          k=s.top();
          ins[k]=0;
          scc++;
          s.pop();
      }
      if(scc!=1)
      {
          maxin=min(maxin,scc);
      }
      
      \(maxin\) 即为结果

    luogu P2863 [USACO06JAN] The Cow Prom S

    • 按照题意打就行
  • 缩点

    • 缩点:把强连通分量看作一个大点,并保留不在此强连通分量内的边,重新建图(易知此时的图是一个 DAG ,可以进行拓扑排序)。
      if(dfn[x]==low[x])
      {
          ans++;
          while(x!=k)
          {
              k=s.top();
              ins[k]=0;
              c[k]=ans;
              b[ans]+=a[k];//a[]为原点权,b[]为缩点后的点权
              s.pop();
          }
      }
      
      cnt=0;
      memset(e,0,sizeof(e));
      memset(head,0,sizeof(head));
      for(i=1;i<=m;i++)
      {
          if(c[u[i]]!=c[v[i]])//Pursuing_OIer 教给我的神奇的建边方法(其实很好理解)
          {
              add(c[u[i]],c[v[i]]);
          }
      }
      
    • 例题
      • luogu P2002 消息扩散 观察题意,易知结果为缩点后入度为0的点个数

      • luogu P2812 校园网络【[USACO]Network of Schools加强版】 第一问显然同luogu P2002,第二问显然是要求 \(max(缩点后入度为0的点的个数,缩点后出度为0的点的个数)\) , 只有一个 \(SCC\) 时记得特判。

        双倍经验 P2746 [USACO5.3] 校园网Network of Schools

      • luogu P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G 观察题意,易知缩点后若有强连通分量的个数 \(>1\) ,则无解;当只有 \(1\) 个强连通分量时,这个强连通分量的大小即为所求。

      • luogu P2515 [HAOI2010] 软件安装 读完题,发现和luogu P2014 [CTSC1997] 选课很像,只不过选课是一棵树,本题是一个图。可能本题建图有点麻烦,剩下的话,缩点后当$树形dp做就行了。

        for(i=1;i<=n;i++)
        {
            cin>>u[i];
            v[i]=i;
            if(u[i]!=0)
            {
                add(u[i],v[i]);
            }
        }
        
        ······
        
        cnt=0;
        memset(e,0,sizeof(e));
        memset(head,0,sizeof(head));
        for(i=1;i<=n;i++)
        {
            if(c[u[i]]!=c[v[i]])
            {
                add(c[u[i]],c[v[i]]);
                din[c[v[i]]]++;
            }
        }
        for(i=1;i<=ans;i++)
        {
            if(din[i]==0)
            {
                add(0,i);
            }
        }
        
      • luogu P3387 【模板】缩点 缩点后跑最长路(SPFA or 拓扑

        int top_sort()
        {
            queue<int>q;
            int i,k,num=0;
            for(i=1;i<=ans;i++)
            {
                if(din[i]==0)
                {
                    q.push(i);
                    dis[i]=b[i];//b[]为缩点后的点权
                }
            }
            while(q.empty()==0)
            {
                k=q.front();
                q.pop();
                for(i=head[k];i!=0;i=e[i].next)
                {
                    dis[e[i].to]=max(dis[e[i].to],dis[k]+b[e[i].to]);
                    din[e[i].to]--;
                    if(din[e[i].to]==0)
                    {
                        q.push(e[i].to);
                    }
                }
            }
            for(i=1;i<=ans;i++)
            {
                num=max(num,dis[i]);
            }
            return num;
        }
        

        加强版->P3627 [APIO2009] 抢掠计划 增加了起点和终点限制(缩点时要进行存储),但总体改变不大,拓扑不太好打,所以就换SPFA了,代码

  • 割点(割顶)

参考资料:

学校的课件(不方便放出)

强连通分量 | 割点和桥 |双连通分量

『学习笔记』Tarjan

强连通分量及缩点tarjan算法解析

『Tarjan算法 无向图的割点与割边』

标签:缩点,Tarjan,连通,笔记,学习,dfn,low,节点,分量
From: https://www.cnblogs.com/The-Shadow-Dragon/p/17548536.html

相关文章

  • 5.2 随机森林在巨量数据中的增量学习
    集成学习是工业领域中应用最广泛的机器学习算法。实际工业环境下的数据量往往十分巨大,一个训练好的集成算法的复杂程度与训练数据量高度相关,因此企业在应用机器学习时通常会提供强大的计算资源作为支持,也因此当代的大部分集成算法都是支持GPU运算的(相对的,如果你发现一个算法在任何......
  • 2023烟台7天编程集训笔记
    sort函数:把数组从小到大排序max函数:求出两个数的最大值min函数:求出两个数的最小值unique函数:使用前提是先排好序,再使用,效果是去重merge_sort归并排序reverse函数:翻转数组random_shuffle函数:把a[1]到a[n]随机打乱swap函数:交换两个数没有单调性不能二分位运算运行速度比加......
  • Django框架学习-Celery的使用
    celery用户文档:https://docs.celeryq.dev/en/v5.3.1/userguide/index.html1、Celery的提出用户需要在网站填写注册信息,发给用户一封注册激活邮件到邮箱,如果由于各种原因,这封邮件发送所需时间较长,那么客户端将会等待很久,造成不好的用户体验。——> 将耗时任务放到后台异步执行,从......
  • [刷题笔记] Luogu P4017 最大食物链计数
    ProblemDescription首先明确,最大食物链指生产者到顶级消费者(即最高营养级),而不是最长的食物链这样,我们就可以将题意转化为:在一张图中,求入度为0的点到出度为0的点路径数量这不妥妥的拓扑排序嘛(这题竟然在dp训练题单里,想了好久的dp)Solution虽说是拓扑排序,但并不完全是。我们......
  • 数据结构学习2
    5、线性表的链式存储结构①定义链式存储:用一组任意的存储单元存储线性表中的数据元素。线性链表:用这种方法存储的线性表简称线性链表。特点:结点在存储器中的位置是随意的,即在逻辑上相邻的数据元素在物理上不一定相邻。实现:为了正确表示结点间的逻辑关系,在存储每个结点......
  • SRS之StateThreads学习
    最近在看SRS的源码。SRS是基于协程开发的,底层使用了StateThreads。所以为了充分的理解SRS源码,需要先学习一下StateThreads。这里对StateThreads的学习做了一些总结和记录。StateThreads是什么StateThreads是一个用户级线程库,用于多线程编程。它提供了一种轻量级的线程模型,允许开......
  • 深入理解计算机系统 笔记——第二章
    第二章信息的表示和处理三种重要的数字表示无符号(unsigned),基于传统的二进制表示法,表示大于等于零的数字补码(two'scomplement),表示有符号整数的最常见的方法浮点数(floatingpoint),表示实数的科学计数法的以2为基数的版本整数的表示虽然只能编码一个相对较小的数值范围,但是......
  • [刷题笔记] Luogu P3183 食物链
    ProblemDescription通俗一点就是在一张图上求入度为0的点到出度为0的点路径的个数。Solution简要题意后发现可以拓扑排序?这里主要介绍记忆化搜索。记忆化搜索是指记住当前节点的状态,待下次搜到这里直接调用即可,无需继续搜索。显然由记忆化搜索延伸到dp,但如果状态设计很复杂......
  • PE学习
    1、主要结构体DOSMZ文件头的内存大小为64个字节DOSStub的大小不确定,因为这段是给连接器用的,即使这段数据被删改也不影响运行通过DOSMZ文件头尾到PE文件头的内存确定大小DOS部分属于是历史遗留问题,用于DOS操作系统与exe程序运行无关,只是保留在PE中 PE文件头由三个部......
  • redis学习十九:redis复制
    定义:主从复制,master以写为主,slave以读为主当master数据变化的时候,自动将新的数据异步同步到其他slave数据库作用:1.读写分离2.容灾备份3.数据备份4.水平扩容支撑高并发如何实现:配从库不配主库权限细节:master如果配置了requirepass参数,需要密码登录那么slave就需要配置ma......