浏览器标题切换
浏览器标题切换end

LibreOJ10091 - 受欢迎的牛 - Tarjan模板题

补一下之前学的


这些代码都是一样的
POJ2186(英文版) = OpenJ_Bailian - 2186(英文版) = 计蒜客T2956(中文) = LibreOJ10091(中文)

学习视频链接

https://www.bilibili.com/video/av60380978?p=1
(可以看视频画的图)

关键点

最最最重要的就是dfn和low这两个数组

dfn[x]:x点DFS到的时间(即时间戳),(注意:记录的是首次搜索到的序号)(记录被搜索到的先后次序)。

low[x]:x点及其后代指出去的边 能返回到的最浅的点的时间戳。 --> (因为如果x能搜索到比自己小的时间戳的点,那么就说明形成了一个环)

scc[x]:表示x属于哪一个强连通。

如果一个点没有指出去的边了,(不能往下了,不能指向更浅的地方了,所以指向它自己),说明该点是一个强连通分量。

当x点的dfn[x] = low[x],说明以x为根的子树(该点及其后代)是一个强连通分量

思路

DFS+栈维护

栈:维护一个SCC中有哪些点

时间复杂度:\(O(V+E)\)

图的存储算法

这个模板用的是邻接表,还可以学一下下面这些:

邻接矩阵 数组表示

邻接表 链表表示 主要是有向图

十字链表 链表表示 主要是有向图

邻接多重表 链表表示 主要是无向图

AC代码:

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<cmath>
#include<list>
#include<stdlib.h>
#include<map>
#include<stack>
#include<stdio.h>
#include<queue>

using namespace std;
typedef long long ll;
#define sc(T) scanf("%d",&T)
#define scc(x,y) scanf("%d %d",&x,&y)
#define pr(T) printf("%d\n",T)
#define f(a,b,c) for (int a=b;a<=c;a++)
#define ff(a,b,c) for (int a=b;a>=c;a--)
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
#define eps 1e-9
#define PI acos(-1)

const int N=1e4+20;
const int M=5e4+20;
int n,m,num,id,tot=0,top;
int head[N],de[N],si[N],scc[N],st[N];
int dfn[N],low[N];

struct node
{
    int nex,v;
}e[M];

void add(int u,int v)
{
    e[++tot].nex=head[u];
    head[u]=tot;
    e[tot].v=v;
}

void tarjan(int u)  //tarjan缩点
{
    if(dfn[u])
        return;    //说明被搜到过/访问到过
    dfn[u]=low[u]=++id; //id记录访问到的序号 (序号一直在增加的)
    st[++top]=u;//入栈 记录该点之后的子树是有哪些和它一起构成scc的
    for(int i=head[u];i!=-1;i=e[i].nex) // 遍历这个点连出去的边
    {
        int v=e[i].v;
        if(!dfn[v]) //如果该点未访问过
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!scc[v]) //如果还在栈内
            low[u]=min(low[u],dfn[v]); //更新low数组 为什么更新:需要去找到low数组维护的该点指出去的最小的时间戳(dfn值)
    }

    // 该节点所连的边全部遍历完之后 (无法走去其他点 (有向图))
    // 判断是否是scc,不是的话该函数走完,是的话开始回溯找到该节点所连接的点构成一个scc
    if(low[u]==dfn[u]) //这个根组成的是不是一个scc 如果后代不能找到更浅的点
    {
        // 相等的话就是一个强联通分量
        // 那么以该点点为根的就是一个scc
        scc[u]=++num; //num是scc的个数
        ++si[num];
        while(st[top]!=u) // 从栈中弹出节点,一直到是u节点为止 因为以它为根是一个scc
        {
            ++si[num];
            scc[st[top--]]=num; //找到 st栈 数组 弹出
        }
        --top;
//        num++;
//        for(;;)
//        {
//            int x=st[top--];
//            scc[x]=num;
//            if(x==u)
//                break;
//        }
    }
}

int main()
{
    while(~scc(n,m))
    {
        mem(head,-1);
        mem(dfn,0);
        mem(scc,0);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scc(x,y);
            add(y,x);//反向建边
        }
        for(int i=1;i<=n;i++)
            tarjan(i);
        for(int i=1;i<=n;i++)
        {
            for(int j=head[i];j!=-1;j=e[j].nex)
            {
                if(scc[i]!=scc[e[j].v])
                {
                    de[scc[e[j].v]]++;
                }
            }
        }
        int ans=0,u=0;
        for(int i=1;i<=num;i++)
        {
            if(!de[i])
            {
                ans=si[i];
                u++;
            }
        }
        if(u==1)
            pr(ans);
        else
            pr(0);
    }
    return 0;
}
posted @ 2020-08-22 21:50  抓水母的派大星  阅读(163)  评论(0编辑  收藏  举报