• 关于tarjan


    关于Tarjan算法

    梗概

    tarjan算法有两种(我了解的),一种是用来求强连通分量的,另一种是关于割点和桥的问题。

    根据机房大佬HL说过,这两种算法是互相独立的,只是代码很像。


    强连通分量问题

    关于这类tarjan算法,我了解到的主要的一个应用就是缩点。

    例题传送门

    思路

    首先,如果我们考虑,如果这是一个有向无环图,我们可以用拓扑排序(DP?)的方法直接求出答案。

    但是这个图是一个有向有环图,无法直接采用拓扑排序。

    这时候我们想为什么无法直接用拓扑排序,因为在有环的情况下我们无法直接确定各个点的拓扑序。

    如果我们把每个环都替换成一个点,点权为环中点权的总和,这就是一个有向无环图。

    怎么样才可以找到这些环呢?这时候就可以拿出tarjan了。

    tarjan算法

    tarjan算法有两个关键的数组:dfn[]和low[]。

    dfn[i]表示搜索到第i个点时的时间戳(第几个搜到的)。

    low[i]表示第i个点所在的环中的所有点的最小dfn[]。

    上代码:

    void dfs(int x){
        dfn[x]=low[x]=++Tt;q[++t]=x;vis[x]=1;//Tt为时间戳 q[]为栈数组
      										//vis[i]表示i点是否在栈中
        for(int i=lnk[x];i;i=la[i]){
            int y=ne[i];
            if(!dfn[y]){dfs(y);low[x]=min(low[x],low[y]);}
          	//如果该节点没被遍历过,就查看x是否与y在同一个环中。
            else if(vis[y])low[x]=min(low[x],dfn[y]);
        	  //否则,就查看x与y是否成环。
        }
        if(low[x]==dfn[x]){
            ++cnt;
            int y=0;
            while(y!=x){y=q[t];ty[y]=cnt;A[cnt]+=a[y];vis[y]=0;--t;}
          //这里为染色的过程,所有环都有一个颜色,后面根据每个点的颜色重新建图。
        }
    }
    

    重新建图完毕后就可以直接进行拓扑排序。

    参考程序

    #include<bits/stdc++.h>
    using namespace std;
    int read(){
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
        while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
        return x*f;
    }
    int dfn[10005],low[10005],a[10005],q[100005],A[10005],ty[10005],t,tot,cnt,n,m,h,ans,Tt;
    int ne[100005],la[100005],lnk[10005],Ne[100005],La[100005],Lnk[10005],du[10005],dis[10005];
    bool vis[300005];
    void add(int x,int y){ne[++tot]=y;la[tot]=lnk[x];lnk[x]=tot;}
    void Add(int x,int y){Ne[++tot]=y;La[tot]=Lnk[x];Lnk[x]=tot;}
    void dfs(int x){
        dfn[x]=low[x]=++Tt;q[++t]=x;vis[x]=1;
        for(int i=lnk[x];i;i=la[i]){
            int y=ne[i];
            if(!dfn[y]){dfs(y);low[x]=min(low[x],low[y]);}
            else if(vis[y])low[x]=min(low[x],dfn[y]);
        }
        if(low[x]==dfn[x]){
            ++cnt;
            int y=0;
            while(y!=x){y=q[t];ty[y]=cnt;A[cnt]+=a[y];vis[y]=0;--t;}
        }
    }
    void bfs(){
        h=t=0;
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=cnt;++i)if(du[i]==0&&(!vis[i]))q[++t]=i,dis[i]=A[i],vis[i]=1;
        while(h<t){
            int u=q[++h];
            for(int k=Lnk[u];k;k=La[k]){
                du[Ne[k]]--;
                if(dis[Ne[k]]<dis[u]+A[Ne[k]])dis[Ne[k]]=dis[u]+A[Ne[k]];
                if(du[Ne[k]]==0&&(!vis[Ne[k]]))q[++t]=Ne[k];
            }
        }
        for(int i=1;i<=cnt;++i)ans=max(ans,dis[i]);
    }
    int main(){
        n=read(),m=read();for(int i=1;i<=n;++i)a[i]=read();
        for(int i=1;i<=m;++i){int x=read(),y=read();add(x,y);}
        tot=0;for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
        tot=0;for(int i=1;i<=n;++i)for(int j=lnk[i];j;j=la[j])if(ty[i]!=ty[ne[j]])Add(ty[i],ty[ne[j]]),++du[ty[ne[j]]];
        bfs();
        cout<<ans;
        return 0;
    }
    

    割点和桥问题

    首先了解几个概念

    割点 :在去掉这个点以及它所关联的边后,图从连通变为不连通,这个点就被称为割点。

    :在去掉这条边后,图从连通变为不连通,这条边就被称为桥。

    例题传送门

    思路

    这一类问题题目中的条件是无向图。这是和上面一种Tarjan的一个很大的区别。

    以下是求割点的代码。

    void tar(int u,int fa){//fa作为根节点作为一个特殊的情况
    	dfn[u]=low[u]=++cnt;int son=0;
    	for(int j=lnk[u];j;j=la[j]){
    		int v=ne[j];
    		if(!dfn[v]){//找出了一个环
    			tar(v,fa);
    			low[u]=min(low[u],low[v]);
    			if(low[v]>=dfn[u]&&u!=fa)cut[u]=1;//如果u的v子树和u不成环u为一个割点
    			if(u==fa)++son;
    		}
    		low[u]=min(low[u],dfn[v]);
    	}
    	if(son>1&&u==fa)cut[fa]=1;
    }
    

    接下来是求桥的代码。

    void tar(int u){
    	dfn[u]=low[u]=++sum;
    	for(int j=lnk[u];j;j=la[j]){
    		vis[j]=1;
    		if(!vis[j^1]){
    			int v=ne[j];
    			if(!dfn[v]){
    				tar(v);
    				low[u]=min(low[u],low[v]);
    				if(dfn[u]<low[v])cut[j]=cut[j^1]=1;
    			}else low[u]=min(low[u],dfn[v]);
    		}
    	}
    }
    

    求割点和求桥的在代码上区别其实就一个等于号,如果背板子的话也比较简单。

    例题参考程序:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=20005,M=200005;
    int read(){
    	int x=0;char c=getchar();
    	while(c<'0'||c>'9')c=getchar();
    	while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    	return x;
    }
    int n,m,ans;
    int ne[M],la[M],lnk[N],tot;
    int dfn[N],low[N],cnt;
    bool cut[N];
    void add(int x,int y){ne[++tot]=y;la[tot]=lnk[x];lnk[x]=tot;}
    void tar(int u,int fa){
    	dfn[u]=low[u]=++cnt;int son=0;
    	for(int j=lnk[u];j;j=la[j]){
    		int v=ne[j];
    		if(!dfn[v]){
    			tar(v,fa);
    			low[u]=min(low[u],low[v]);
    			if(low[v]>=dfn[u]&&u!=fa)cut[u]=1;
    			if(u==fa)++son;
    		}
    		low[u]=min(low[u],dfn[v]);
    	}
    	if(son>1&&u==fa)cut[fa]=1;
    }
    int main(){
    	n=read();m=read();
    	for(int i=1;i<=m;++i){
    		int x=read(),y=read();
    		add(x,y);add(y,x);
    	}
    	for(int i=1;i<=n;++i)if(!dfn[i])tar(i,i);
    	for(int i=1;i<=n;++i)if(cut[i])++ans;printf("%d
    ",ans);
    	for(int i=1;i<=n;++i)if(cut[i])printf("%d ",i);
    	return 0;
    }
    
  • 相关阅读:
    Git
    Git
    Git
    Git
    Docker
    Linux
    Linux
    Python
    Python
    SQL
  • 原文地址:https://www.cnblogs.com/ADMAN/p/10561435.html
Copyright © 2020-2023  润新知