流图:存在点r,从r出发可以达到有向图中的每一个点,则该图成为流图
一些基本概念:
1.树枝边(x,y):指搜索树中的边,即x是y的父亲
2.前向边(x,y):指搜索树中x是y的祖先结点
3.后向边(x,y):指搜索树中y是x的祖先结点
4.横叉边(x,y):除以上三种情况外的边,可以证明dfn[y]<dfn[x]
证明:如果dfn[y]>dfn[x],那么x必定比y先访问到,同时从x可以到y,那么这条边就变成了前向边或树枝边
有向图的强连通分量SCC
tarjan算法和求无向图的双联通分量不同,需要额外开一个栈,栈中需要保存一下两类结点
1.搜索树上x的祖先结点,记为anc[x]
设y是x的祖先,并且有后向边(x,y),则x,y之间有环
2.已经访问过,并且存在一条路径达到anc[x]的点
设从z出发可以达到anc[x],并且存在一条横叉边(x,z),那么y->x->z->y就是一个环
如何求追溯值low
1.当x第一次被访问时,low[x]=dfn[x],x入栈
2.遍历以x为起点的所有边(x,y)
a.如果y没有被访问过,则继续递归,递归结束后low[x]=min(low[x],low[y])
b.若y被访问过且在栈中,那么low[x]=min(low[x],dfn[y])
3.遍历结束后判断dfn[x]==low[x],如果是,那么找到了一个强连通分量,把栈中所有在x以上的结点弹出即可
int c[N],out[N],cnt;//新图的点个数,每个点的出度 int ind,dfn[N],low[N],stk[N],top,ins[N]; void tarjan(int x){ dfn[x]=low[x]=++ind; stk[++top]=x;ins[x]=1; for(int i=head[x];i!=-1;i=e[i].nxt){ int y=e[i].to; if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); } else if(ins[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]){ cnt++; int y; do{ y=stk[top--]; ins[y]=0; c[y]=cnt; }while(x!=y); } }
scc的缩点:类似于e_DCC的缩点
int main(){ //... for(int x=1;x<=n;x++) for(int i=head[x];i!=-1;i=edge[i].nxt){ int y=edge[i].to; if(c[x]==x[y])continue; add_c(c[x],c[y]); } //... }