图论 -Tarjan算法

  • 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
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]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
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);
    }
}

你可能感兴趣的:(算法心得)