文末有福利。
Tarjan是通过搜索树和压栈完成的,维护两个东西:dfn[i](时间戳)、low[i](通过搜索树外的边i(返祖边),节点能到达的最小节点的时间戳)。
跑完Tarjan,缩点,可以得到DAG图(有向无环图),可以再建图或统计入度出度。
在有向图中,可以找强连通分量SCC(极大强联通子图)(任意两点可以互达):
多维护一个vis【i】表示在不在栈中。
1 void tarjan_(int u) 2 { 3 stack[++tp]=u; 4 dfn[u]=low[u]=++num; 5 vis[u]=1; 6 for(int i=head[u];i;i=ed[i].nxt) 7 { 8 int v=ed[i].to; 9 if(!dfn[v]) 10 { 11 tarjan_(v); 12 low[u]=min(low[u],low[v]); 13 } 14 else if(vis[v]) 15 { 16 low[u]=min(low[u],dfn[v]); 17 } 18 } 19 if(dfn[u]==low[u]) 20 { 21 int temp; 22 scc_num++; 23 do 24 { 25 temp=stack[tp--]; 26 vis[temp]=0; 27 siz[scc_num]++; 28 co[temp]=scc_num; 29 }while(temp!=u); 30 } 31 }
无向图中,可以找割点(在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。)、割边(也叫桥)(图G的边e是割边,当且仅当e不在G的任何一个圈上)。又可由此得出点双连通分量(v-DCC)(一个无向图中不存在割点)和边双连通分量(e-DCC)(若一个无向图中不存在割边(桥))。
简单说就是:
割点:去掉这个点,原本连通的图变得不连通。
割边:去掉这条边,原本连通的图变得不连通。
点双:不存在割点的子图。
边双:不存在割边的子图。
粘代码:
点双:
attention:
- 用vector存,不能像其他一样用co【i】来染色,因为因为一个割点可能在多个点双中。
- 割点也要放在每一个与之相连的点双中。
- 缩点后新图中,是割点与点双间隔排列地连接的。每一个点双中有几个割点就说明新图中该节点有几条连边。
- 【重中之重】判断割点的 if(low[y]>=dfn[x]) 在枚举每条边的循环中,因为一个割点可能在多个点双中。
1 int root,cnt; 2 bool cut[maxn];//cut【i】==1表示节点i是割点 3 void tarjan(int x) 4 { 5 dfn[x]=low[x]=++cnt; 6 stack[++tp]=x; 7 int flag=0; 8 for(int i=head[x];i;i=ed[i].nxt) 9 { 10 int y=ed[i].to; 11 if(!dfn[y]) 12 { 13 tarjan(y); 14 low[x]=min(low[x],low[y]); 15 if(low[y]>=dfn[x]) 16 { 17 flag++; 18 if(root!=x||flag>1) cut[x]=true;//搜索树的跟需要特殊处理,必须有两个子树才是割点 19 dcc_num++; 20 int temp; 21 do{ 22 temp=stack[tp--]; 23 dcc[dcc_num].push_back(temp); 24 }while(temp!=y); 25 dcc[dcc_num].push_back(x); 26 } 27 } 28 else low[x]=min(low[x],dfn[y]); 29 } 30 } 主函数中:
for(int i=1;i<=n;i++) { if(!dfn[i]) root=i,tarjan(i); }
边双:
attention:
- 由于是无向图,故与SCC不同,需要记录来时的点或边。
- 由于可能有重边的情况,记录来时的点就不行啦,就要像我一样记录来时的边(链式前向星存图时从2开始存,故每一对边的序号关系为a^1==b,b^1==a(亦或))。
- 与点双不同,判断要放在循环之外。
- 除此之外,还有另一种写法:求出割边后dfs。
1 bool bri[maxm<<1]; 2 int dfn[maxn],low[maxn],stack[maxn],tp,dcc_num; 3 int cnt,co[maxn]; 4 void tarjan(int x,int pre_ed) 5 { 6 dfn[x]=low[x]=++cnt; 7 stack[++tp]=x; 8 for(int i=head[x];i;i=ed[i].nxt) 9 { 10 if(i==(pre_ed^1)) continue; 11 int y=ed[i].to; 12 if(!dfn[y]) 13 { 14 tarjan(y,i); 15 low[x]=min(low[x],low[y]); 16 } 17 else low[x]=min(low[x],dfn[y]); 18 } 19 if(low[x]==dfn[x]) 20 { 21 dcc_num++; 22 int temp; 23 do{ 24 temp=stack[tp--]; 25 co[temp]=dcc_num; 26 }while(temp!=x); 27 } 28 }
福利来了:
例题(模板题)
SCC:P2863 [USACO06JAN]牛的舞会The Cow Prom P2341 [HAOI2006]受欢迎的牛 P1726 上白泽慧音 P2746 [USACO5.3]校园网Network of Schools
点双:P3225 [HNOI2012]矿场搭建 P2783有机化学之神偶尔会做作弊 P3469 [POI2008]BLO-Blockade
边双:P2860 [USACO06JAN]冗余路径Redundant Paths
部分内容借鉴GMK大佬课件,表示感谢。
原文地址:https://www.cnblogs.com/sdfzjdx/p/10503486.html