9.求强连通分量个数(缩点)~tarjan算法

上篇讲了拓扑排序只适用于有向无环图,那么tarjan算法就是把有向有环图变成一个有向无环图的算法

上述过程也就是缩点,是将原来的一个强连通分量缩成一个点,理由很简单,我只要有了这个强连通分量内的任意一点,我就可以到达强连通分量内的任意一个点,所以把他们一个整体看成一个点,由于环或者说回路全被合并成整体了,剩下的图自然就是有向无环图了!(不明白的自己百度强连通分量定义)

先介绍两个数组,明白一下他们的意义(建议强记,然后结合下面代码来看)

dfn数组,俗称的时间戳,代表此点是第几个被遍历到的

low数组,代表能追溯到的最早的已经被遍历过的点的序号

pan数组,并查集思想,初始化为pan[i]=i

下面直接贴一下tarjan函数

stack q;//建立一个栈
void tarjan(int u)
{
    low[u]=dfn[u]=++ant;//点u被遍历到了,所以把low和dfn初始化了,dfn初始化之后的值就不会在改变,但是low是可能改变的,因为此时还没有朝下遍历,不知道是否能回溯到之前被遍历过的点,所以初始化为自己的序号
    q.push(u);//u点被遍历了,扔进栈
        used[u]=1;//标记进栈
    for(int i=head[u];i;i=bian[i].qian)//以u为起点遍历相连的点
    {
        int v=bian[i].wei;
        if(!dfn[v])//dfn我初始化全为0,如果v没有被遍历过
        {
            tarjan(v);那就递归进去
            low[u]=min(low[u],low[v]);u能追溯的最早的点当然就是自己和v点能追溯到的最早的点中最早的一个了,dp的思想
        }
        else if(used[v])//如果v点已经被遍历过了,并且在栈里
        low[u]=min(low[u],low[v]);//那low[u]就和low[u]做对比
//是不是发现了盲点,除了v点被遍历过并且不在栈中,其他时候都要比较low[u]和low[v],当v点已经确定在某一强连通分量时,在比较会让low[u]变小,使结果错误
    }
    if(low[u]==dfn[u])//如果时间戳和low相等,这意味着什么,意味他能回溯到的最早的点就是他自己,u点向下搜索饶了一圈又回到了自己(或者说无法回溯到自己或者比自己更早的点),说明u就是强连通分量的一个起始搜索点,此时栈里在u点之上的点都属于这个强连通分量
    {
        while(q.top()!=u)//现在要把栈里面的点提出来
        {
                        used[q.top()]=0;//标记出栈
            pan[q.top()]=u;//此时他们的祖宗不在是自己,而是u(并查集思想)
            q.pop();//出栈
             
        }
        q.pop();//u点自己也是要出的
                used[u]=0;
    }
}

多解释一下,为什么当已经确定一个点在强连通分量里面(即used[v]=0且dfn[v]不为0的时候不能比较low[u]和low[v]),比如现在有1 2 3三个点,1指向2,2指向1,3指向1,从1开始遍历,显然1 2是一个强连通分量,1 2遍历完后得到,low[1]=dfn[1]=1,low[2]=1,dfn[2]=2,used[2]=0,used[1]=0,从3开始遍历,dfn[3]=3,3显然是一个单独的强连通分量,如果强行比较,会使得正确结果low[3]=dfn[3]=3变成dfn[3]=3,low[3]=1,显然这个答案就错误了

下面是洛谷缩点模板原题

image.png

标准的缩点题,先缩点成一个有向无环图,累加权值给强连通分量,然后用拓扑排序dp处理权值和,下面我只贴一份缩点的代码,拓扑排序有兴趣大家可以补充一下交一下这个题:https://www.luogu.com.cn/problem/P3387

#include
#include
#include
#include
using namespace std;
struct xing
{
    int qian,wei;
}bian[101000];
int n,m,cnt,head[11000],low[11000],dfn[11000],ant,ans,pan[11000],o1,o2,mem[11000];
bool used[11000];
void add(int u,int v)
{
    bian[++cnt].qian=head[u];
    bian[cnt].wei=v;
    head[u]=cnt;
}
stack q;
void tarjan(int u)
{
    low[u]=dfn[u]=++ant;
    q.push(u);
    used[u]=1;
    for(int i=head[u];i;i=bian[i].qian)
    {
        int v=bian[i].wei;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(used[v])
        low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        ans++;
        while(q.top()!=u)
        {
            used[q.top()]=0;
            pan[q.top()]=u;
            mem[u]+=mem[q.top()];//权值累加
            q.pop();
        }
        q.pop();
        used[u]=0;
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i>mem[i];//每个点的权值
    }
    for(int i=1;i>o1>>o2;
        add(o1,o2);//连边
    }
    for(int i=1;i

你可能感兴趣的:(9.求强连通分量个数(缩点)~tarjan算法)