强连通分量与缩点(Tarjan算法)(洛谷P3387)

名词解释:

强连通分量:

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

简单点说,即在有向图中对于某个子图S,如果S中的点两两都直接或间接的相连,则称这个子图为强连通分量。(要求最大的满足条件的子图)

缩点:

把一个强连通分量用一个点代替的操作称为缩点。

算法实现

对于缩点,我们采用Tarjan算法来实现它。
Tarjan其实就是一个DFS的过程。

所需记录的量

开一个栈来记录dfs的轨迹。

对于节点i,记录dfn[i]表示i是第几个被访问的,low[i]表示以i为父亲的节点能访问到的在栈中最前面的节点。(即i指向的节点的最小非零dfn值,因为会有环存在,所以上述情况是有可能发生的)。

遍历过程

代码解释

void tarjan(int x){//当前遍历到x
    dfn[x]=low[x]=++p;//初始化dfn与low
    stack[++top]=x;//入栈
    f[x]=true;//标记入栈
    for (int i=h1[x];i;i=ed[i].next)//访问其儿子
        if (!dfn[ed[i].to]){//如果还没有做过
            tarjan(ed[i].to);//继续遍历
            low[x]=min(low[x],low[ed[i].to]);//递归完成后更新low值
        }
        else //如果已经做过
            if (f[ed[i].to])//如果在栈内 
                low[x]=min(low[x],dfn[ed[i].to]);//更新low值
    if (low[x]==dfn[x]){//如果到底
        f[x]=false;//判其出栈
        k++;
        while (stack[top+1]!=x){//栈要弹到当前节点位置
        //在这里可以更新一些其他的东西
            num[stack[top]]=k;//给新的强连通分量标号
            f[stack[top--]]=false;//出栈
        }
        num[x]=k;//当前节点也属于新的强连通分量
    }
}

模板

缩点配合最短(长)路算法可以用于求有向图中路径上的最大点权和。

以洛谷P3387为例:

#include
#include
#include
#define MAXN 10000
using namespace std;
struct edge{
    int next;
    int to;
};
int n,m,k,p,top,ans;
int dfn[MAXN+5],low[MAXN+5],stack[MAXN+5],num[MAXN+5];
int dis[MAXN+5],que[MAXN*10+5],h1[MAXN+5],h2[MAXN+5],w[MAXN+5];
edge ed[MAXN*20+5];
bool f[MAXN+5];
inline char readc(){//读优
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline int _read(){
    int num=0; char ch=readc();
    while (ch<'0'||ch>'9') ch=readc();
    while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
    return num;
}
void addedge(int x,int y,int *h){
    ed[++k].next=h[x];
    ed[k].to=y;
    h[x]=k;
}
void tarjan(int x){//缩点
    dfn[x]=low[x]=++p;
    stack[++top]=x;
    f[x]=true;
    for (int i=h1[x];i;i=ed[i].next)
        if (!dfn[ed[i].to]){
            tarjan(ed[i].to);
            low[x]=min(low[x],low[ed[i].to]);
        }
        else 
            if (f[ed[i].to]) low[x]=min(low[x],dfn[ed[i].to]);
    if (low[x]==dfn[x]){
        f[x]=false;
        while (stack[top+1]!=x){
            num[stack[top]]=x;//这里直接把所有点都缩到祖先上
            f[stack[top--]]=false;
        }
        num[x]=x;
    }
}
void spfa(int now){//求最大点权和
    int l=0,r=1; que[1]=now; dis[now]=w[now]; ans=max(ans,dis[now]);
    while (lint x=que[++l];
        f[x]=false;
        for (int i=h2[x];i;i=ed[i].next)
            if (dis[ed[i].to]if (!f[ed[i].to]){
                    f[ed[i].to]=true; que[++r]=ed[i].to;
                }
            }
    }
}
int main(){
    n=_read(); m=_read();
    for (int i=1;i<=n;i++)
        w[i]=_read();
    for (int i=1;i<=m;i++){
        int u=_read(),v=_read();
        addedge(u,v,h1);
    }
    for (int i=1;i<=n;i++)//要保证每个点都被缩过
        if (!dfn[i])
            tarjan(i);
    for (int i=1;i<=n;i++)
        if (num[i]!=i)
            w[num[i]]+=w[i];
    for (int i=1;i<=n;i++)
        for (int j=h1[i];j;j=ed[j].next)
            if (num[i]!=num[ed[j].to])//如果他们不属于一个强连通分量
                addedge(num[i],num[ed[j].to],h2);//重新建边
    for (int i=1;i<=n;i++)
        if (num[i]==i&&!dis[i])
            spfa(i);
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(洛谷,算法/总结/游记,蒟蒻zxl的Blog专栏)