n(1<=n<=10000) 头牛,有 m(1<=m<=50000) 个推举关系。推举关系具有传递性,即 A 推举 B, B 推举 C,那么 A 也推举 C
问你,能够得到所有牛推举的牛有多少头?
题解:强连通分支+缩点
强连通分支为最大的连通子图,在这个子图中任意两点都是可达的。
在一个强连通分支里面,根据推举关系可知,这个强连通分支里的所有的牛都能得到其他所有的牛的推举。
我们将题目给的推举关系,看成是牛群推举图里的一条边,而且是有向的,而且可以有环。
对于环,在环中的任意点,环中其它的点都是可以到达这个点的。
而这个环就相当于一个强连通分支。
而缩点的意义在于,我们可以将推举图变成推举树,这样做的意义在于,如果这棵树的叶子节点只有一个,则这个节点在原推举图中包含了多少个点,那么其值就是我们要求的答案。如果这棵树的叶子节点大于一个,那么答案肯定为0.
因此,此题有三个步骤是关键,一,求强连通分支,二、将强连通分支缩成点。三、求叶子节点
对于一、求强连通分支的方法又tarjan算法。
对于二、我们可以在tarjan算法里增加一段缩点代码。就是在找到强连通分支的时候。
对于三、只要出度为0,就是叶子节点。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <stack> using namespace std; const int MAX_ = 10010; typedef long long LL; vector<int>arc[MAX_]; stack<int>s; int dfn[MAX_], low[MAX_], cnt[MAX_], id[MAX_]; int n, m, T, ind; bool vs[MAX_]; void tarjan(int u){ vs[u] = 1, s.push(u); dfn[u] = low[u] = T++; int len = (int)arc[u].size(); for(int i = 0; i < len; i++){ int v = arc[u][i]; if(dfn[v] == -1){ tarjan(v); if(low[u] > low[v]) low[u] = low[v]; } else if(vs[v] && dfn[v] < low[u]) low[u] = dfn[v]; } if(dfn[u] == low[u]){ for(int v; 1 ;){ v = s.top(); s.pop();vs[v] = 0; cnt[ind]++,id[v] = ind; if(u == v)break; } ind++; } } int main() { //int Case, n, h,v, tmp, maxh,minh, maxv,minv; //int x1,y1,x2,y2; scanf("%d%d",&n,&m); for(int i = 0; i <= n; i++)arc[i].clear(); for(int i = 0, a, b; i < m; i++){ scanf("%d%d",&a,&b); arc[a].push_back(b); } for(int i = 0; i <= n; i++)vs[i] = 0, dfn[i] = -1,cnt[i] = 0; while(!s.empty())s.pop(); T= ind = 0; for(int i = 1; i <= n; i++){ if(dfn[i] == -1)tarjan(i); } for(int i = 0; i < ind; i++)dfn[i] = 0; for(int i = 1; i <= n ; i++){ int len = (int) arc[i].size(), u= id[i]; for(int j = 0; j < len; j++){ int v = id[arc[i][j]]; if(u != v)dfn[u]++; } } int ans =0,flag =0; for(int i = 0; i < ind; i++){ if(dfn[i] == 0){ ans = cnt[i]; flag ++; if(flag > 1)break; } } if(flag != 1)ans = 0; printf("%d\n",ans); return 0; }