1 条题解

  • 0
    @ 2025-8-24 21:42:25

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 冈崎梦美
    **

    搬运于2025-08-24 21:42:25,当前版本为作者最后更新于2018-05-13 13:54:40,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    1.1 简述强联通分量与Tarjan

    在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量。

    说白了就是如果一个有向图的子图里每个点可以两两互达,那么这个子图是一个强联通分量

    Tarjan算法就是基于DFS求强联通分量的算法。

    2.1 Tarjan思想

    2.1.1 Tarjan维护的变量

    在Tarjan算法中我们维护如下的变量:

    vector<int> G[maxn];//图本身(邻接表)
    stack<int>s;//栈,存放答案
    int vis[maxn];//标记点是否在栈中
    int dfn[maxn];//节点i的时间戳
    int low[maxn];//节点i能够回溯到的最早位于栈中的节点。(子树的根,可以理解为并查集的“祖先”一类的东西)
    int color[maxn];//每个点属于第几个强联通分量
    int colornum;//强连通分量的个数
    int cnt;//当前时间
    

    2.2.2 Tarjan算法运行过程

    1. 按照深度优先遍历的方式遍历这张图。

    2. 遍历当前节点所出的所有边。在遍历过程中:

      ( 1 ) 如果当前边的终点还没有访问过,访问。

      回溯回来之后比较当前节点的low值和终点的low值。将较小的变为当前节点的low值。(因为遍历到终点时有可能触发了2)

      ( 2 ) 如果已经访问过,那我们一定走到了一个之前已经走过的点(终点的时间戳一定比当前的小)

      则比较当前节点的low值和终点的dfn值。将较小的变为当前节点的low值

    3. 在回溯过程中,对于任意节点u用其出边的终点v的low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。

    4. 当一个节点的dfn值和low值相等时,这个节点是一个强联通分量的“根”。压栈,输出。

    ~~我知道这让你听得很迷糊,~~先来一段伪代码看看吧。

    void tarjan(int u)//当前节点
    {
    	dfn[u]=low[u]=++cnt;//该节点本身是一个强联通分量
        节点入栈;
        vis[u]=true;//当前节点已入栈
        for(遍历该节点所有出边)
        {
        	int v=当前边的终点;
            if (!dfn[v])
            {
            	tarjan(v);//深度优先遍历
                low[u]=min(low[u],low[v]);
            }
            else low[u]=min(dfn[v],low[u]);
        }
        if (low[u]==dfn[u])
        {
        	while(栈顶!=v)
            {
                染色;
            	出栈;
            }
        }
        染色;
        出栈;
    }
    

    手模一组数据就不模啦,网上到处都是。

    3.1 完整code

    这里以洛谷P2863 [USACO06JAN]牛的舞会The Cow Prom为例。题意为:给定一个图,要求图中节点数大于一的强联通分量个数。 对于这道模板题,我们应当做到一遍A掉。

    #include<bits/stdc++.h>
    #define maxn 10001
    using namespace std;
    vector<int>G[maxn];
    stack<int>s;
    int n,m;
    int dfn[maxn],used[maxn],vis[maxn],low[maxn],color[maxn],num[maxn],colornum=0,cnt=0,ans=0;
    void paint(int x)
    {
        s.pop();
        color[x]=colornum;
        num[colornum]++;
        vis[x]=false;
    }
    void tarjan(int x)
    {
        dfn[x]=low[x]=++cnt;
        s.push(x);
        vis[x]=used[x]=true;
        for(int i=0;i<G[x].size();i++)
        {
            int q=G[x][i];
            if (!dfn[q])
            {
                tarjan(q);
                low[x]=min(low[x],low[q]);
            }
            else if (vis[q]) low[x]=min(low[x],dfn[q]);
        }
        if (low[x]==dfn[x])
        {
            colornum++;
            while(s.top()!=x)
            {
                int t=s.top();
                paint(t);
            }
            paint(x);
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int u,v;
            cin>>u>>v;
            G[u].push_back(v);
        }
        for(int i=1;i<=n;i++)
        {
            if (!used[i]) tarjan(i);
        }
        for(int i=1;i<=colornum;i++)
        {
            if (num[i]>1) ans++;
        }
        cout<<ans;
        return 0;
    }
    

    3.2 我接下来学习什么算法?

    缩点。 P2341 [HAOI2006]受欢迎的牛可以作为一道模板题。

    敬请期待。

    • 1

    信息

    ID
    1928
    时间
    1000ms
    内存
    128MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者