一.原题链接:http://poj.org/problem?id=2762
二.题目大意:给你一个有向图,问能不能在图中任意选2个点u, v,使得可以从u到v或者从v到u。
三.思路:
1.预备知识:
(1)在一个强连通分量里面,任意两点都有双向的路径。这是强连通分量的定义。
(2)求一个图强连通分量方法:
tarjan算法:详情:http://blog.csdn.net/h992109898/article/details/51318135
这题跟里面的tarjan算法不一样,不过差不多,也是利用dfn[]和low[]数组,用来求强连通分量。读完上面的博客,大概了解了dfn[]与low[]数组,这题dfn[]是用来标记搜索顺序,low[]用来标记可以到达的最高层祖先处。
求强连通分量的步骤是:每次进入DFS,直接将当前点进栈,然后扫所有的邻接点,有向图不用考虑是不是刚进来的边(跟回边混淆),没有访问过的,继续搜索,退出来之后取low[u] = min(low[u], low[v]),u为当前节点,v为邻接点。访问过的,说明一定有回边,low[u] = min(low[u], dfn[v])。扫完所有的边之后,如果dfn[u] == low[u],说明以u为根节点的深度优先搜索子树上的结点构成了一个强连通分量,把所有点出栈,缩点(待会再说)。为什么会这样呢?因为u肯定是可以到达子树上的所有结点,因为u是根部,而且每个子结点都可以到达u,因为每个子节点通过若干个回边,能回到u。假设没有结点到达u,那么子结点所回到的祖先结点,会自己变成一颗深度优先搜索树的根部,然后自己先弹栈。注意这里在DFS的时候,不能DFS(u, depth+1),而一定要设一个变量,每次进入DFS就加1,这2者不一样的,因为前者会出现多个dfn[u]的值相同,会产生混淆。(反正我改完就AC了)。然后还是不知道为什么会这样。
2.思路切入:我们可以把每个强连通分量看成一个点,点内中随便找2个点肯定可以互通的(强连通分量定义)。然后通过桥构成一个新图,此时要判断的就是这个新图上任取2点u, v,能不能从u到达v或者从v到达u,记住是或者不是并且。然后其实只要进行一次拓扑排序,拓扑排序过程中如果出现入度为0的点同时有1个以上(不包括一个),则选这2个点就不满足条件,如果这2个点代表2个连通分量,那么选它们之中分别的任意2点都不满足条件,假设它们之间有路径,路径只有一个中间节点t,那么要使得2个点入度同时为0,那么必须先把t删除,因为要把t删除了,t连向2个点之中一个点的出边才会删除,要删除t必须删除另外一个节点,因为这样t的入度才为0,于是矛盾了。用数学归纳法可证明路径有多个节点的情况。
为什么不一开始就拓扑排序,而是要先找连通分量缩点呢?难道强连通分量可以拓扑排序?找得到入度为0的点?
四.代码:
#include <iostream> #include <cstdio> #include <queue> #include <cstring> #include <vector> #include <stack> using namespace std; const int MAX_N = 1558, INF = 0x3f3f3f3f; int nodeNum, edgeNum, cntId, visNum, id[MAX_N], low[MAX_N], dfn[MAX_N]; vector <int> edge[MAX_N], tree[MAX_N]; bool visited[MAX_N], inStack[MAX_N]; stack <int> st; void init() { int i; visNum = 0; for(i = 0; i < 1024; i++) edge[i].clear(), tree[i].clear(); memset(visited, 0, sizeof(visited)); memset(inStack, 0, sizeof(inStack)); memset(tree, 0, sizeof(tree)); while(!st.empty()) st.pop(); cntId = 0; } void dfs(int u) { visited[u] = true; low[u] = dfn[u] = visNum++; st.push(u); inStack[u] = true; int i, v; for(i = 0; i < edge[u].size(); i++){ v = edge[u][i]; if(!visited[v]){ dfs(v); low[u] = min(low[u], low[v]); } else if(inStack[v]) low[u] = min(low[u], dfn[v]); } if(low[u] == dfn[u] && inStack[u]){ int tmp; do{ tmp = st.top(); st.pop(); inStack[tmp] = false; id[tmp] = cntId; }while(tmp != u); cntId++; } } bool topoSort() { int u, i, v, k, cnt[MAX_N], j, cntIn, save; bool mark[MAX_N]; memset(cnt, 0, sizeof(cnt)); memset(mark, 0, sizeof(mark)); for(i = 0; i < cntId; i++) for(j = 0; j < tree[i].size(); j++) cnt[tree[i][j]]++; cntIn = 0; for(i = 0; i < cntId; i++) if(0 == cnt[i]){ cntIn++; save = i; } if(cntIn > 1) return false; for(k = 0; k < cntId; k++){ cntIn = 0; u = save; for(i = 0; i < tree[u].size(); i++){ v = tree[u][i]; cnt[v]--; if(0 == cnt[v]){ cntIn++; save = v; } } if(cntIn > 1) return false; } return true; } bool judge() { int i, j, v; for(i = 1; i <= nodeNum; i++) if(!visited[i]) dfs(i); for(i = 1; i <= nodeNum; i++){ for(j = 0; j < edge[i].size(); j++){ v = edge[i][j]; if(id[i] != id[v]) tree[id[i]].push_back(id[v]); } } return topoSort(); } int main() { //freopen("in.txt", "r", stdin); int test, i, j, u, v; scanf("%d", &test); while(test--){ init(); scanf("%d%d", &nodeNum, &edgeNum); for(i = 0; i < edgeNum; i++){ scanf("%d%d", &u, &v); edge[u].push_back(v); } if(judge()) printf("Yes\n"); else printf("No\n"); } return 0; }