• Tarjan


    Tarjan

    割点 & 割边(桥)

    先引入两个概念:时间戳追溯值

    时间戳

    在 $ dfs $ 遍历图时,每个节点第一次被访问的顺序,就是这个点的 时间戳,通常用 (dfn[u]) 表示;

    追溯值

    节点 $ u $ 的追溯值 定义为以下节点的 时间戳 的最小值;

    1.$ subtree(u) $ 中的节点;

    2.通过 1 条不在搜索树上的边,能够到达 $ subtree(u) $ 的节点;

    通常用 $ low[u] $ 表示;

    割边

    无向连通图中,去掉一条边,图中的连通分量数增加,则这条边,称为桥或者割边,当然也是在无向图。

    无向边 $ (x,y) $是桥, 当且仅当搜索树上存在 $ x $ 的一个节点 $ y $ ,满足: $ dfn[x] < low[y] $

    需要注意的是,因为我们遍历的是无向图,所以从每个点 $ x $ 出发,总能访问到它的父节点 $ fa $ ,根据 $ low $ 的计算方法,$ (x,fa) $ 属于搜索树上的边

    且 $ fa $ 不是 $ x $ 的子节点,所以不能用 $ fa $ 的时间戳来更新 $ low[x] $ ;

    但是如果有重边的情况,那么 $ fa $ 与 $ x $ 之间不在搜索树上的边是可以更新 $ low[x] $ 的;

    所以我们需要知道 $ fa $ 与 $ x $ 之间的边是不是搜索树上的边,那么我们可以利用成对变换技巧就可以了;

    void tarjan(int u,int in){
    	dfn[u]=low[u]=++tot;
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){ //是否是子树上的点
    			tarjan(to,i);
    			low[u]=min(low[u],low[to]);
    			if(dfn[u]<low[to]){
                    //.......
    			}
    		}else if(i!=(in^1)){ //判断是否是搜索树上的边
    			low[u]=min(low[u],dfn[to]);
    		}
    	}
    }
    

    割点

    如果在一个图中,如果把一个点删除,那么这个图不再连通,那么这个点就是割点(割顶),当然是在无向图。

    若 $ x $ 不是搜索树的根节点,则 $ x $ 是割点当且仅当搜索树上存在 $ x $ 的一个子节点 $ y $ ,满足:$ dfn[x] <= low[y] $

    若 $ x $ 是搜索树的根节点,则 $ x $ 是割点当且仅当搜索树上存在至少两个子节点 $ y1,y2 $ ,满足以上条件;

    可以发现父节点和重边的情况是不必考虑的 ,想想为什么

    void tarjan(int u){
    	dfn[u]=low[u]=++tot;
    	int flag=0;
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){
    			tarjan(to);
    			low[u]=min(low[u],low[to]);
    			if(dfn[u]<=low[to]){
    				flag++;
    				if(u!=1||flag>1) cut[u]=1;// 根节点特殊的情况
    			}
    		}else low[u]=min(low[u],dfn[to]);
    	}
    }
    

    P3388 【模板】割点(割顶)

    P3225 [HNOI2012]矿场搭建

    模板:割点和桥

    边双连通分量

    在一张连通的无向图中,对于两个点 $u $ 和 $ v $ ,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 (u)(v) 边双连通

    边双连通的极大子图称为边双连通分量

    任意一条边至少在一个简单环中

    边双连通分量怎么求?

    先求出那些边是桥,再 $ dfs $ 就可以了

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=1e5+7;
    const int M=3e5+7;
    struct edge{
    	int v,nxt;
    }e[M<<2];
    int n,m,tot,cnt=1,ans;
    int head[N],dfn[N],low[N],bri[N],vis[10000000];
    void add_edge(int u,int v){
    	e[++cnt]=(edge){v,head[u]};
    	head[u]=cnt;
    }
    
    void tarjan(int u,int in){
    	dfn[u]=low[u]=++tot;
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){
    			tarjan(to,i);
    			low[u]=min(low[u],low[to]);
    			if(dfn[u]<low[to]){
    				bri[i]=bri[i^1]=1;
    			}
    		}else if(i!=(in^1)){
    			low[u]=min(low[u],dfn[to]);
    		}
    	}
    }
    
    void dfs(int u,int fa){
    	vis[u]=fa;
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(vis[to]||bri[i]) continue;
    		dfs(to,fa);
    	}
    }
    
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		int x,y;
    		scanf("%d%d",&x,&y);
    		add_edge(x,y);
    		add_edge(y,x); 
    	}
    	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0);
    	for(int i=1;i<=n;i++){
    		if(!vis[i]){
    			dfs(i,++ans);
    		}
    	}
    	cout<<ans; 
    }
    

    点双连通分量

    点双连通分量是一个极大的子图,满足图中删去任何一个点都不会改变图的连通性。

    也就是不存在割点

    性质

    1.点双连通分量中没有割点。

    2.若干个点双连通分量以割点相连接。

    3.原图中的每个割点可能属于多个点双连通分量,其它点只可能属于一个点双连通分量。所以出栈时不能全部出栈

    4.任意两点至少存在两条边不重复路径.

    求法

    1.当一个节点第一次被访问时,将该点入栈。

    2.当找到割点时,无论 $ u $ 是否为根,从栈中不断弹出节点,知道 $ to $ 节点被弹出;

    3.弹出的节点与 $ u $ 构成一个 $ v - DCC $ 。

    看代码

    void tarjan(int u){
    	dfn[u]=low[u]=++tot;
    	if(u==rt&&head[u]==0){
    		dcc[++cnt].push_back(u); //孤立点
    		return;
    	}
    	st[++h]=u; //入栈
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){
    			tarjan(to);
    			low[u]=min(low[u],low[to]);
    			if(dfn[u]<=low[to]){ //是割点
    				cnt++;
    				int k;
    				do{
    					k=st[h];
    					h--;
    					dcc[cnt].push_back(k);
    				}while(k!=to); // 注意这里是to,不是u,因为割点可能在多个 v-DCC 中
    				dcc[cnt].push_back(u);
    			}
    		}else low[u]=min(low[u],dfn[to]);
    	}
    }
    

    UVA1364 Knights of the Round Table

    圆方树

    在圆方树中,原来的每个点对应一个圆点,每一个点双对应一个方点
    所以共有 $ n+c $ 个点,其中 $ n $ 是原图点数,$ c $ 是原图点双连通分量的个数。

    而对于每一个点双连通分量,它对应的方点向这个点双连通分量中的每个点连边。
    每个点双形成一个“菊花图”,多个“菊花图”通过原图中的割点连接在一起(因为点双的分隔点是割点)。

    显然,圆方树中每条边连接一个圆点和一个方点。

    圆方树的点数小于 $ 2n $,这是因为割点的数量小于 $ n $,所以请注意各种数组大小要开两倍。

    其实,如果原图连通,则“圆方树”才是一棵树,如果原图有 $ k $ 个连通分量,则它的圆方树也会形成 $ k $ 棵树形成的森林。

    就把图上问题转化成了树上问题,那怎么构建圆方树呢,重新建边就好了,见代码;

    void tarjan(int u){
    	dfn[u]=low[u]=++tot;
    	st[++h]=u;
    	num++;
    	for(int i=head[u];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){
    			tarjan(to);
    			low[u]=min(low[u],low[to]);
    			if(dfn[u]<=low[to]){
    				int k;
    				++n;
    				w[n]++;
    				add(u,n); // 重新建边
    				add(n,u);
    				do{
    					k=st[h];
    					h--;
    					w[n]++;
    					add(k,n); 
    					add(n,k); 
    				}while(to!=k);
    			}
    		}else low[u]=min(low[u],dfn[to]);
    	}
    }
    

    P4630 [APIO2018] Duathlon 铁人两项

    P4606 [SDOI2018]战略游戏

    有向图的强连通分量

    有向图 G 强连通是指,G 中任意两个结点连通。强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。

    tarjan 学习网址

    $ dfn $ : 在 $ dfs $ 遍历图时,每个节点第一次被访问的顺序,就是这个点的 时间戳

    追溯值

    节点 $ u $ 的追溯值 定义为以下节点的 时间戳 的最小值;

    1.栈中的节点;

    2.存在一条从子树出发的有向边,以改点为终点;

    可以发现,$ dfn[u] $ 必然大于等于 $ low[u] $ ,当 $ dfn[u] = low[u] $ 时 说明这个节点是当前这个强联通量的最小的 $ dfn $ ,就可以弹栈了;

    void tarjan(int now){
    	dfn[now]=low[now]=++cnt;
    	st[++h]=now;
    	vis[now]=1; //判断是否在栈中
    	for(int i=head[now];i;i=e[i].nxt){
    		int to=e[i].v;
    		if(!dfn[to]){
    			tarjan(to);
    			low[now]=min(low[now],low[to]);
    		}else if(vis[to]){
    			low[now]=min(low[now],dfn[to]);
    		}
    	}
    	int k;
    	if(dfn[now]==low[now]){
    		++cur;
    		do{
    			k=st[h],h--;
    			vis[k]=0;
    			id[k]=cur,sum[cur]+=a[k];
    		}while(now!=k);
    	}
    }
    

    P3119 [USACO15JAN]Grass Cownoisseur G

    P5008 [yLOI2018] 锦鲤抄

  • 相关阅读:
    青少年机器人技术等级考试实际操作试卷(三级)201812 new
    SQL预编译 new
    青少年机器人技术等级考试实际操作试卷(三级)201809 new
    青少年机器人技术等级考试实际操作试卷(三级)201803 new
    SQL基本练习 new
    Asp.Net MVC 自定义一个ActionResult用于AJAX交互
    使用 TeamLab 来协同和管理工作
    使用Chose来美化Select
    在Asp.Net下使用couchbase实现分布式缓存
    如何修改couchbase的RAM
  • 原文地址:https://www.cnblogs.com/Aswert/p/14273854.html
Copyright © 2020-2023  润新知