题目大意:有N头牛,他们都喜欢膜拜其他牛,有M种膜拜关系,问有多少头牛被其他所有的牛膜拜。
分析:这个问题的模型就是,给出一个有向图,有多少个顶点可以被其他所有顶点达到。在DAG(有向无环图)中,只有出度为0的
点,才能被其他所有点到达。由于无环,所以从任何点出发,都将终止于出度为0的点。
首先,我们求出所有的强连通分量。 这里我们用Korasaju算法,简单地说就是两次dfs。第一次dfs,先从任意一个顶点开始遍历所有尚未访问过
的点,并在回溯前给顶点标号,其实就是后序遍历所有节点啦~标号完成后,我们会发现越接近搜索树的叶子,顶点标号越小。然后,就是第二次dfs,
先将所有边反向,这种所有边反向的图,又称转置图。从标号最大的顶点作为起点,开始dfs,这样遍历到的顶点集合就构成了一个强连通分量。依次
dfs尚未访问的节点,就可以得到剩余的强连通分量。
很明显有这样一个事实,至多只有一个强连通分量满足条件。而在得到所有强连通分量的同时,我们还可以得到他们的拓扑序,也就是说只有拓
扑序的最后一个强连通分量才可能满足条件(不满足的情况,就是缩点后的DAG上有多于一个的出度为0的点)。
接着,再将每个强连通分量缩成一个点,这样就形成了一个DAG了。缩点不一定要构成新图,给不同的强连通分量染不同颜色即可。
最后,我们只需要利用转置图判断这个强连通分量是否能到达所有点,能的话,那么从原图的其他顶点都能到达这个强连通分量,答案就是这个
强连通分量的顶点个数。
代码:
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int maxn = 11111; vector<int> g[maxn]; //原图 vector<int> rg[maxn]; //转置图 vector<int> vs; //后序遍历得到的顶点列表 bool vis[maxn]; int color[maxn]; //顶点v属于哪一个强连通分量 int n, m; void dfs(int u) { vis[u] = 1; int len = g[u].size(); for(int i = 0; i < len; i++) if(!vis[g[u][i]]) dfs(g[u][i]); vs.push_back(u); } void rdfs(int u, int k) { vis[u] = 1; color[u] = k; //这里就是缩点了 int len = rg[u].size(); for(int i = 0; i < len; i++) if(!vis[rg[u][i]]) rdfs(rg[u][i], k); } int scc() { memset(vis, 0, sizeof(vis)); vs.clear(); for(int i = 0; i < n; i++) if(!vis[i]) dfs(i); memset(vis, 0, sizeof(vis)); int k = 0; for(int i = vs.size()-1; i >= 0; i--) //从顶点标号最大的顶点开始第二次遍历,因为从0开始编号,所以减一 if(!vis[vs[i]]) rdfs(vs[i], k++); return k; } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 0; i < m; i++) { int u, v; scanf("%d%d", &u, &v); g[u-1].push_back(v-1); rg[v-1].push_back(u-1); } int num = scc(); int u = 0, ans = 0; for(int i = 0; i < n; i++) if(color[i] == num-1) //判断当前顶点是否属于最后一个强连通分量 u = i, ans++; memset(vis, 0, sizeof(vis)); rdfs(u, 0); //利用转置图从最后一个强连通分量开始遍历所有定 for(int i = 0; i < n; i++) if(!vis[i]) { //只要有一个顶点没有被访问,则说明无解 ans = 0; break; } printf("%d\n", ans); } return 0; }