浅析Tarjan算法在信息竞赛中的运用。

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Tarjan
有两类: 1.连通性问题中,用来求解割点和桥,以及缩点使原图划分为森林。 2.LCA 离线算法(此类在 LCA 中讨论) 。 原理,通过两个标记数组来判别割点和桥,并在 dfs 过程中用栈来记录每个双联 通分量(或是强连通分量)中的点或者边,最后按这些记录缩点建图后是森林。 两个标记数组分别是 dfn[] low[] dfn[]数组记录的是,在 dfs 树中,某点在 dfs 过程中此点出现的早晚,越小出现 的越早 low[]数组表示此点及其后代能回到的标号(dfn)最小的点,即此点或其后代能 回到的最浅的点(dfs 树中) 。
int v=edge[i].v; if(ef[i]) continue; ef[i]=ef[i^1]=1; st[++top]=i;//边入栈,需注意此语句要放在判 continue 之后 if(!dfn[v]){ //如果 v 节点未去过,搜索 v 节点 child++; tarjan(v,root); low[root]=min(low[root],low[v]); //更新 low 值 if(dfn[root]<=low[v]){
low[root]=min(low[root],low[v]);//更新 low 值 if (dfn[root]<low[v]){
//连接 root 与 v 的边是桥,注意判断时的符号
} } else low[root]=min(low[root],dfn[v]); //与有向图区分,此处 else 不需要判别 v 节点是否在栈内 } if(low[root]==dfn[root]){ bccnum++;//得到一个 BCC for(;;){ int x=st[top--];//节点出栈,并标记其 BCC bcc[x]=bccnum; if(x==root)break; } } }
E[++esum]=make_pair(edge[j].v,N); } if(bj[edge[j^1].v]!=N){ bj[edge[j^1].v]=N; E[++esum]=make_pair(edge[j^1].v,N); } belong[(j>>1)+1]=N;//标记边所属的 bcc if(i==j)break; } } } else low[root]=min(low[root],dfn[v]);
Code[模板]:
无向图求桥,边双联通缩点
//有自环时不加自环的边 //边双连通缩点方法:清空路径,枚举原图每条边,看 ex[i]与 ey[i]两个节点所属 的 BCC 是否相同,如果不同则此边为桥,在新图中建立双向边 void tarjan(int root){ dfn[root]=low[root]=++index;//新点初始化 st[++top]=root; //节点入栈 gson(i,root){//遍历 root 指出去的边 int v=edge[i].v; if(ef[i])continue; ef[i]=ef[i^1]=1; //标记走过的边 if(!dfn[v]){//如果 v 节点未去过,搜索 v 节点 tarjan(v);
对于缩点建图后,其结构如下: 1. SCC 缩点建图后,依旧是有向图,但其中没有回路存在。 2. 边 BCC 缩点建图后成森林,森林中的边都是桥。 3. 点 BCC 缩点建图后成森林, 原来的点被保留, BCC 与 BCC 之间必定经过割点, 割点与割点不会直连(不能用来判桥,因为桥会缩成 BCC) ,除了割点的原来 的点只有一条边,且连着其属于的 BCC。 点 BCC 缩点建图后森林示意图如下:
无向图求割点,点双联通缩点
//有自环时不加自环的边 //点双连通缩点方法:清空路径,枚举 E[]数组中存储的路径,建立双向边。 void tarjan(int root,int fa){ dfn[root]=low[root]=++idx; //新点初始化 int child=0;
//初始节点需要两个以上儿子且 dfn[root]<=low[v] 才是割点 gson(i,root){ //遍历 root 指出去的边
有上述两个数组定义可知:对于某点 root,其有一儿子 v,则有: 1. 如果 dfn[root]<=low[v]此点是割点(对于 dfs 树的根,即最初节点 需要两个 儿子才是割点) 2. 如果 dfn[root]<low[v],连接 root 与 v 的边是桥。 根据 dfs 过程中, 依据栈内存储的边或者点来划分双联通分量或是强连通分量 (具 体划分看代码注释) ,对于双联通分量(Bid Connected Component) ,有以下几个 注意点: 1. 划分后的的的单元块有大环存在,但并不是每个点都在大环上。 2. 对于边双联通分量,删除任意一边连通性不变,其中可能含有割点,且其中 的环与环不保证有公共边,但一定至少有 1 个公共点 。 3. 对于点双联通分量,删除任意一点连通性不变,其中不含桥,环与环必定含 有公共边,且公共点至少两个,简单圈中的点一定属于同一个点 BCC,一般 题中所谓的环路(circle)是指简单圈。
//与有向图区分,此处 else 不需要判别 v 节点是否在栈内
Leabharlann Baidu
} if(root==fa && child<2)isCut[root]=0;
//如果初始节点没有 2 个以上儿子,标记清 0
}
有向图强连通分量
//有自环时不加自环的边 //强连通如果缩点剩下的图依旧是有向图,但保证无环 void tarjan(int root){ dfn[root]=low[root]=++index;//新点初始化 stak[++top]=root; //节点入栈
gson(i,root){ //遍历 root 指出去的边
int v=edge[i].v; if(!dfn[v]){ //如果 v 节点未去过,搜索 v 节点 tarjan(v); low[root]=min(low[root],low[v]); //更新 low 值 } else if(!scc[v]){ //如果 v 节点还在栈内,更新 low 值 low[root]=min(low[root],dfn[v]); } } if(low[root]==dfn[root]){ sccnum++;//得到一个 SCC for(;;){ int x=stak[top--];//节点出栈,并标记其 SCC scc[x]=sccnum; if(x==root)break; } } }
//此点是割点,需注意初始节点要有两个儿子 N++;//注意这里是 N++,建数组时要注意开至少两倍大
for(;;){ int j=st[top--];
//bj[]数组用来标记节点所属的 bcc,割点会改变,无意义 //E[]存新图的边,esum 是其数量,tarjan 结束后建双向边
if(bj[edge[j].v]!=N){ bj[edge[j].v]=N;
相关文档
最新文档