强连通分支+拓扑排序
求强连通分支有两种方法,korasaju和tarjan。
korasaju是进行两次dfs覆盖全图(实际上是两种dfs,覆盖全图需要多次dfs),第一次给结点标起始和结束时间,第二次把图反向并从结束时间最大的结点开始dfs,每次dfs所能到达的结点为一个强连通分支。下面来简单讲一下为什么这样做。第二次dfs过程中每次dfs的起点为结束时间最大的点,这样就可以保证是第一次dfs中的某一棵搜索树的根节点。那么从任意一个第一次dfs的根节点开始的对反图的dfs都不可能跨越到在第一次dfs中比该根节点更早的搜索树中。因为假设能跨越,说明反图中晚的搜索树有指向更早搜索树的边,即原图中有从更早搜索树指向晚搜索树的边。那么晚搜索树根本就不会单独成树,与之前假设矛盾。这样就保证了第二次dfs过程中每次dfs只可能在第一次的某一棵子树中进行,不可能跨树搜索。第二次能根u搜到的点v,说明原图中v可以到达u,而第一次v一定是u树中的结点,也就是说u可到达v,从而一定能形成强连通分支。
tarjan算法则是利用一次dfs,整个过程中对每个结点记录两个值,dfn[u]结点的访问时间,low[u]结点及其子节点所能访问到的结点v中dfn[v]的最小值。每次把访问到的结点入栈,一旦搜索完某一结点u的所有子节点后发现low[u]==dfn[u]则弹栈直到u被弹出,此过程弹出的点为一个强连通分支。这样也就保证了,栈中所有结点都是可以到达某一父节点的,也就是说一旦某个点可以到达栈中某个点,它一定可以到达某父节点。
题意:给出一个有向图,要求该图中任意两节u,v,要么能从u走到v,要么能从v走到u。问是否满足。
分析:
强连通分支中的点一定能互相到达。不属于同一强连通分支的点就用拓扑排序来判断,如果某次删除点的时候发现两个入度为0的点,则说明这两个点只能由已经被删掉的点到达,也就是说这两个点互相不可达。
得到强连通分支后重新建图的方法是,新建一个存储图的空间。用每个强连通分支的编号作为新图中结点的编号。判断原来的每条边的两端点是否属于同意强连通分支,若不属于则在新图中加入一条连接两个强连通分支编号对应结点的边。
View Code #include < iostream > #include < cstdlib > #include < cstring > #include < cstdio > using namespace std; #define maxn 1005 #define maxm 5005 struct Edge{ int v, next;} edge[maxm], opedge[maxm], newedge[maxm]; int n, m, head[maxn], ophead[maxn], ncount, nowtime, pos[maxn * 2 ], sig[maxn], signnum, in [maxn], newhead[maxn]; bool flag[maxn], g[maxn][maxn]; void addedge( int a, int b){ edge[ncount].v = b; edge[ncount].next = head[a]; head[a] = ncount; opedge[ncount].v = a; opedge[ncount].next = ophead[b]; ophead[b] = ncount; ncount ++ ;} void input(){ scanf( " %d%d " , & n, & m); for ( int i = 0 ; i < m; i ++ ) { int a, b; scanf( " %d%d " , & a, & b); a -- ; b -- ; addedge(a, b); }} void dfs( int a){ flag[a] = true ; pos[nowtime] = a; nowtime ++ ; for ( int i = head[a]; i != - 1 ; i = edge[i].next) if ( ! flag[edge[i].v]) dfs(edge[i].v); pos[nowtime] = a; nowtime ++ ;} void rdfs( int a){ flag[a] = true ; sig[a] = signnum; for ( int i = ophead[a]; i != - 1 ; i = opedge[i].next) if ( ! flag[opedge[i].v]) rdfs(opedge[i].v);} bool ok(){ bool found; int a; memset(flag, 0 , sizeof (flag)); while ( 1 ) { found = false ; for ( int i = 0 ; i < signnum; i ++ ) if ( in [i] == 0 && ! flag[i]) { if (found) return false ; found = true ; flag[i] = true ; a = i; } if ( ! found) break ; for ( int i = newhead[a]; i != - 1 ; i = newedge[i].next) in [newedge[i].v] -- ; } return true ;} int main(){ // freopen("D:\\t.txt", "r", stdin); int t; scanf( " %d " , & t); while (t -- ) { ncount = 0 ; memset(head, - 1 , sizeof (head)); memset(ophead, - 1 , sizeof (ophead)); memset(flag, 0 , sizeof (flag)); input(); nowtime = 1 ; for ( int i = 0 ; i < n; i ++ ) if ( ! flag[i]) dfs(i); memset(flag, 0 , sizeof (flag)); signnum = 0 ; for ( int i = 2 * n; i > 0 ; i -- ) if ( ! flag[pos[i]]) { rdfs(pos[i]); signnum ++ ; } memset(g, 0 , sizeof (g)); memset( in , 0 , sizeof ( in )); memset(newhead, - 1 , sizeof (newhead)); ncount = 0 ; for ( int i = 0 ; i < n; i ++ ) for ( int j = head[i]; j != - 1 ; j = edge[j].next) if (sig[i] != sig[edge[j].v] && ! g[sig[i]][sig[edge[j].v]]) { int a = sig[i]; int b = sig[edge[j].v]; g[a][b] = true ; in [b] ++ ; newedge[ncount].v = b; newedge[ncount].next = newhead[a]; newhead[a] = ncount; ncount ++ ; } if (ok()) printf( " Yes\n " ); else printf( " No\n " ); } return 0 ;}