zoukankan      html  css  js  c++  java
  • Tarjan算法与割点割边

    Tarjan算法与无向图的连通性

    1:基础概念

    在说Tarjan算法求解无向图的连通性之前,先来说几个概念:

    <1. 时间戳:在图的深度优先遍历中,按照每一个结点第一次被访问到的时间顺序,依次给予N个结点1~N的整数边集,该标记就被计位“时间戳”,计做 (dfn[x])

    <2. 搜索树:任选一个结点深度优先遍历,每个点只访问一次。产生递归的边构成的树为搜索树。

    ❤️. (subtree(x)):搜索树中以x为根的子树。

    <4. 追溯值:追溯值((low[x]))定义为以下结点的时间戳的最小值,这些结点满足:

    ①:是(subtree(x))的结点; ②:通过一条不在搜索树上的边,能够到达(subtree(x))的结点

    在了解概念之后,我们可以根据定义来计算(low[x])

    (low[x]=dfn[x]);然后考虑每个从x的出边( x, y ),如果x是y的父节点,则(low[x]=min(low[x],low[y]));如果边不是搜索树上的边,则令(low[x]=min(lwo[x],dfn[y])); //ps:后面代码也会提到

    2:Tarjan判断割点

    判定定理:对于一个点 x ;如果x不为根节点,那么x是割点当且仅当搜索树上存在x 的一个子节点y,满足:

    [dfn[x]<=low[y] ]

    如果x是根节点,那么搜索树上至少存在两个子节点满足上述性质。

    代码模板(求割点个数,及从小到大输出):

    #include<bits/stdc++.h>
    
    using namespace std;
    const int maxn=1e5+10;
    int head[maxn],n,m,tot;
    struct Edge
    {
    	int nex,to;
    }edge[maxn<<1];
    void add(int from,int to)
    {
    	edge[++tot].to=to;
    	edge[tot].nex=head[from];
    	head[from]=tot;
    }
    int dfn[maxn],low[maxn],st[maxn],idx,root;
    bool cut[maxn];
    void tarjan(int u)							//tarjan求一下dfn和low
    {
    	dfn[u] = low[u] = ++idx;				//初始化dfn和low
    	int flag=0;
    	for(int i=head[u];i!=-1;i=edge[i].nex)
    	{
    		int v=edge[i].to;
    		if(dfn[v])	low[u]=min(low[u],dfn[v]);	//定义,追溯值为子树最小的时间戳
    		else{
    			tarjan(v);							//找子节点的子节点
    			low[u]=min(low[u],low[v]);
    			if(dfn[u]<=low[v]){					//判定条件
    				flag++;
    				if(u!=root||flag>1)	cut[u]=true;
    			}
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d %d",&n,&m);
    	memset(head,-1,sizeof(head));
    	tot=0;
    	for(int i=1;i<=m;++i){
    		int a,b;
    		scanf("%d %d",&a,&b);
    		if(a==b)	continue;
    		add(a,b);
    		add(b,a);
    	}
    	for(int i=1;i<=n;++i){
    		if(!dfn[i]){
    			root=i;
    			tarjan(i);
    		}
    	}
    	int res=0;				//个数
    	for(int i=1;i<=n;++i)
    		if(cut[i])	res++;
    	printf("%d
    ",res);
    	for(int i=1;i<=n;++i)
    		if(cut[i])	printf("%d ",i);
    	printf("
    ");
    }
    

    如果要求连通分量数,还要写个dfs即可

    void dfs(int u)
    {
        vis[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].nex){
            int v=edge[i].to;
            if(vis[v])  continue;
            vis[v]=1;
            dfs(v);
        }
    }
    for(int i=1;i<=cnt;++i)
    {
        if(cut[i])
        {
            int son=0;
            memset(vis,0,sizeof(vis));
            vis[i]=1;
            for(int j=head[i];j!=-1;j=edge[j].nex){
                int v=edge[j].to;
                if(vis[v])  continue;
                dfs(v);
                son++;
            }
            printf("%d
    ",son);	//son为连通分量数
        }
    }
    

    3:Tarjan判断割边

    割边判断法则:无向边(x,y)是桥当且仅当搜索树上存在 x 的一个子节点 y ,满足:

    [dfn[x]<low[y] ]

    与割点不同的是,这里不能取等号。这个不等式其实也很好理解,根据定义(dfn[x]<low[y])说明从(subtree(y))出发,在不经过边((x,y))的前提下,不管走哪条边,都无法到达x或比x更早访问的结点。若把边((x,y))删除,则(subtree(y))就像是形成了一个封闭环境,因此边((x,y))是割边。

    #include<bits/stdc++.h>
    
    using namespace std;
    const int maxn=1e5+10;
    struct Edge
    {
    	int nex,to;
    }edge[maxn<<1];
    int n,m,tot,head[maxn];
    void add(int from,int to)		//这里边从下标为2开始存方便异或处理
    {
    	edge[++tot].to=to;
    	edge[tot].nex=head[from];
    	head[from]=tot;
    }
    int bridge[maxn<<1],dfn[maxn],low[maxn],idx;
    void tarjan(int u,int fa)
    {
    	dfn[u]=low[u]=++idx;
    	for(int i=head[u];i!=-1;i=edge[i].nex)
    	{
    		int v=edge[i].to;
    		if(!dfn[v]){
    			tarjan(v,i);
    			low[u]=min(low[u],low[v]);
    			if(dfn[u]<low[v])	bridge[i]=bridge[i^1]=true;
    		}
    		else if(i!=(fa^1))	low[u]=min(low[u],dfn[v]);
    	}
    }
    
    int main()
    {
    	scanf("%d %d",&n,&m);
    	memset(head,-1,sizeof(head));
    	tot=1;							//注意边的下标从2开始储存
    	for(int i=1;i<=m;++i){
    		int a,b;
    		scanf("%d %d",&a,&b);
    		add(a,b);
    		add(b,a);
    	}
    	for(int i=1;i<=n;++i)
    		if(!dfn[i])	tarjan(i,-1);
    	for(int i=2;i<=tot;i+=2)
    		if(bridge[i])	printf("%d %d
    ",edge[i].to,edge[i^1].to);
    	system("pause");
    }
    

    【题意】:给了一幅图,问需要加几条边将这幅图所有的结点都在环上(可能在不同的环上)

    求一下割边,判断一下叶结点的数量,答案即为(num+1)>>1;看代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    const int maxn=1e4+10;
    int head[maxn],tot;
    struct Edge
    {
        int from,nex,to;
    }edge[maxn<<1];
    void add(int from,int to)
    {
        edge[++tot].to=to;
        edge[tot].from=from;
        edge[tot].nex=head[from];
        head[from]=tot;
    }
    int dfn[maxn],low[maxn],idx;
    bool bridge[maxn];
    void tarjan(int u,int fa)           //求割边(桥)
    {
        dfn[u]=low[u]=++idx;
        for(int i=head[u];i!=-1;i=edge[i].nex)
        {
            int v=edge[i].to;
            if(!dfn[v]){
                tarjan(v,i);
                low[u]=min(low[u],low[v]);
                if(dfn[u]<low[v]){
                    bridge[i]=bridge[i^1]=true;
                }
            }
            else if(i!=(fa^1))  low[u]=min(low[u],dfn[v]);		//异或打括号!!
        }
    }
    int n,m,outd[maxn],id[maxn],dcc;     //id为双连通分量编号,outd为出度
    void dfs(int u)                      //跑一遍dfs求一下各个边的双连通分量编号
    {
        id[u]=dcc;
        for(int i=head[u];i!=-1;i=edge[i].nex)
        {
            int v=edge[i].to;
            if(bridge[i]||id[v])  continue; //双连通分量不包括桥
            dfs(v);
        }
    }
    
    int main()
    {
        scanf("%d %d",&n,&m);
        tot=1;
        memset(head,-1,sizeof(head));
        for(int i=1;i<=m;++i){
            int a,b;
            scanf("%d %d",&a,&b);
            add(a,b);
            add(b,a);
        }
        for(int i=1;i<=n;++i)
            if(!dfn[i]) tarjan(i,-1);
        for(int i=1;i<=n;++i)
            if(!id[i]){
                dcc++;
                dfs(i);
            }
        for(int i=2;i<=tot;i+=2)
        {
            int u=edge[i].from,v=edge[i].to;
            if(id[u]!=id[v]){
                outd[id[u]]++;
                outd[id[v]]++;
            }
        }
        int ans=0;                  //叶子结点数量
        for(int i=1;i<=dcc;++i)
            if(outd[i]==1)  ans++;
        printf("%d
    ",(ans+1)>>1);
    }
    
  • 相关阅读:
    【反演复习计划】【bzoj2154】Crash的数字表格
    【反演复习计划】【bzoj3529】数表
    【反演复习计划】【bzoj3994】DZY loves maths
    【反演复习计划】【bzoj3994】约数个数和
    【反演复习计划】【bzoj2818】gcd
    【反演复习计划】【bzoj1011】zap-queries
    BZOJ3991: [SDOI2015]寻宝游戏
    BestCoder Round #75
    BZOJ4417: [Shoi2013]超级跳马
    BZOJ4416: [Shoi2013]阶乘字符串
  • 原文地址:https://www.cnblogs.com/StungYep/p/12252102.html
Copyright © 2011-2022 走看看