图论 -Tarjan算法

来源:互联网 发布:2017年网络热点话题 编辑:程序博客网 时间:2024/06/06 06:57

  • Tarjan算法的引入
    • 算法流程
  • 应用和模板题
    • 洛谷P3387
    • 洛谷P3388

Tarjan算法的引入

“tarjan陪伴强联通分量

生成树完成后思路才闪光

欧拉跑过的七桥古塘

让你 心驰神往”—《膜你抄》

tarjan算法是基于对有向图的深度优先搜索的算法,主要用于求解强连通分量,时间复杂度是线性的 O(n+m)其中n为点数,m为边数。tarjan的算法关键在搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量,具体实现这一算法,我们需要使用一些辅助数组,其意义如下所述:

dfn[ ]:时间戳数组,表示这个点在dfs时被搜索到的顺序;

low[ ]:时间戳数组,表示回溯时点被搜索到的顺序;

flag[ ]:布尔型数组,标记某个点是否被访问;

stack[ ]:模拟栈的功能的数组,某些情况可能不需要这一数组;

算法流程

下面用文字来描述一下算法的具体流程:

1.首先从节点1开始dfs,将遍历到的节点加入栈中,如果发现dfn[u]=low[u],则找到了强连通分量,退栈直到u=v;

2.回溯,发现dfn[v]=low[v],{v}为强连通分量;

3.继续返回节点1搜索,至所有节点都被访问,算法结束

应用和模板题

洛谷P3387

缩点模板

用tarjan求解强连通分量,将强连通分量缩点后重新建图,最后跑一遍spfa求最大值。

#include<bits/stdc++.h>using namespace std;const int MAXM=100010;const int MAXN=10010;int head[MAXM],val[MAXM],x[MAXM],y[MAXM],dis[MAXM],f[MAXM];int dfn[MAXM],low[MAXM],color[MAXM],s[MAXM];int n,m,tmp,t,ct,top,ans;bool flag[MAXN];struct Edge {    int to;    int from;    int next;} edge[MAXM];void init() {    memset(head,0,sizeof(head));    memset(flag,false,sizeof(flag));    memset(dfn,0,sizeof(dfn));    memset(low,0,sizeof(low));    tmp=t=top=ans=0;}void add(int from,int to) {    tmp++;    edge[tmp].to=to;    edge[tmp].from=from;    edge[tmp].next=head[from];    head[from]=tmp;}void tarjan(int now) {    dfn[now]=low[now]=++t;    flag[now]=true;    s[++top]=now;    for(int i=head[now],v=edge[i].to; i>0; i=edge[i].next,v=edge[i].to) {        if(dfn[v]==0) {            tarjan(v);            low[now]=min(low[now],low[v]);        }         else if(flag[v]) {            low[now]=min(low[now],dfn[v]);        }    }    if(dfn[now]==low[now]) {        ct++;        flag[now]=false;        while(s[top+1]!=now){            color[s[top]]=ct;            f[ct]+=val[s[top]];            ans=max(ans,f[ct]);            flag[s[top]]=false;            top--;        }    }}void spfa(int x) {    memset(dis,0,sizeof(dis));    memset(flag,0,sizeof(flag));    dis[x]=f[x];    queue <int> q;    flag[x]=true;    q.push(x);    while(!q.empty()) {        int u=q.front();        q.pop();        flag[u]=false;        for(int i=head[u],v=edge[i].to; i>0; i=edge[i].next,v=edge[i].to) {            if(dis[v]<dis[u]+f[v]) {                dis[v]=dis[u]+f[v];                if(!flag[v]) {                    flag[v]=true;                    q.push(v);                }            }        }    }    for(int i=1; i<=ct; i++) {        ans=max(dis[i],ans);    }}int main() {    init();    scanf("%d %d",&n,&m);    for(int i=1; i<=n; i++) {        scanf("%d",&val[i]);    }    for(int i=1; i<=m; i++) {        int from,to;        scanf("%d %d",&from,&to);        x[i]=from;        y[i]=to;        add(from,to);    }    for(int i=1; i<=n; i++) {        if(dfn[i]==0) tarjan(i);    }    tmp=0;    memset(head,0,sizeof(head));    memset(edge,0,sizeof(edge));     for(int i=1;i<=m;i++){        if(color[x[i]]!=color[y[i]]) add(color[x[i]],color[y[i]]);    }    for(int i=1;i<=ct;i++){        spfa(i);    }    printf("%d\n",ans);    return 0;}

洛谷P3388

求割点的模板

割点就是删去了这个点后图变为不再连通的点

判根节点是否为割点非常简单,对于不是根的节点,如果有 low[v]>=dfn[now],则该节点是割点,注意

cnt不要对重复的点多次计数。

#pragma GCC optimize(3)#include<bits/stdc++.h>using namespace std;const int MAXN=200010;int ans[MAXN],p[MAXN],dfn[MAXN],low[MAXN],head[MAXN];bool res[MAXN],flag[MAXN];int tmp=0,t=0,cnt=0;int n,m;struct Edge {    int to;    int next;} edge[MAXN];void init() {    for(int i=1;i<=n;i++){        head[i]=0;dfn[i]=0;low[i]=0;p[i]=i;    }}void add(int from,int to) {    tmp++;    edge[tmp].next=head[from];    edge[tmp].to=to;    head[from]=tmp;}void tarjan(int now) {    int child=0;    dfn[now]=low[now]=++t;    for(int i=head[now]; i>0; i=edge[i].next) {        int v=edge[i].to;        if(!dfn[v]) {            p[v]=p[now];            tarjan(v);            low[now]=min(low[now],low[v]);            if(now!=p[now]&&low[v]>=dfn[now]){                if(res[now]==false) cnt++;                res[now]=true;            }            if(now==p[now]) child++;        }        low[now]=min(low[now],dfn[v]);    }    if(now==p[now]&&child>=2) {        if(res[now]==false) cnt++;        res[now]=true;    }}int main() {    scanf("%d %d",&n,&m);    init();    for(int i=1;i<=m;i++){        int x,y;        scanf("%d %d",&x,&y);        add(x,y);        add(y,x);    }    for(int i=1;i<=n;i++){        if(!dfn[i]) tarjan(i);    }    printf("%d\n",cnt);    for(int i=1;i<=n;i++){        if(res[i]==true) printf("%d ",i);    }}
原创粉丝点击