【2186】Popular Cows(强联通分支及其缩点)
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 28323 | Accepted: 11459 |
Description
Input
Output
Sample Input
3 3 1 2 2 1 2 3
Sample Output
1
Hint
Source
题目大意是有n头牛,他们有m对特殊的关系
关系A B表示A仰慕B 恩 是感觉怪怪的= =
然后如果A仰慕B B仰慕C 那么A也仰慕C 也就是关系是可传递的
要求找出被其他牛都仰慕的牛的数目
一开始很费解,要不是放在强连通里,实在没法往这方向靠……
其实反过来想的话比较好像,强连通分支的定义——强连通分支中从任何一个点都可以访问到其余各点(有向图)
这样再回到题中 可以得出两个结论
1.如果某个强联通分支中的某只牛被分支外的所有牛都仰慕,也就是说分支外的牛都有一条通向他的路,和他在同一个强连通分支里的所有牛也是满足要求的。
2.如果分支中有某头牛仰慕分支外的牛,那么就不存在被所有牛都仰慕的牛。(可以换种方式来想,如果分支中某头牛仰慕分支外的牛,还被其余牛都仰慕,那么分支外的这头牛也应该被包含在分支内。因为这样就说明分支外的那头牛,会有一条通向分支内的路径,也就是符合强连通分量的定义)
3.经2结论可知,出度为0的强连通分支,就是满足条件的牛群。但如果有两群,就不存在这种牛。因为两个分支间是没有关系的。可以自己画一画看看。
找到方法判断就好办了,首先把所有的强连通分支求出来,缩点后变成一团团的。找出出度为0的缩点。如果存在两个或两个以上,答案就是0。如果之存在一个,那么这个点中的所有牛都是满足题意的牛,统计输出即可。
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <list> #include <algorithm> #include <map> #include <set> #include <stack> #define LL long long #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const double eps = 1e-8; struct Edge { int v,next; }; Edge eg[50050]; int head[10010]; int vis[10010]; //dfs序 int dfn[10010]; //最早可访问到的点 int low[10010]; //缩点的出度 int in[10010]; //缩点是否满足出度为0 bool can[10010]; //缩点的根 int pre[10010]; int n,tp,tm; stack <int> s; void tarjan(int u) { s.push(u); vis[u] = 1; low[u] = dfn[u] = tm++; int v; for(int i = head[u]; i != -1; i = eg[i].next) { v = eg[i].v; //点未被访问过 if(vis[v] == 0) { tarjan(v); vis[v] = 1; low[u] = min(low[u],low[v]); } //点在栈中 else if(vis[v] == 1) { low[u] = min(low[u],dfn[v]); } } //此点为树根 if(low[u] == dfn[u]) { int x = s.top(); while(x != u) { pre[x] = u; s.pop(); x = s.top(); } //标记该分支中各点树根为u pre[x] = u; s.pop(); } } int main() { int u,v; while(~scanf("%d%d",&n,&tp)) { memset(head,-1,sizeof(head)); for(int i = 0; i < tp; ++i) { scanf("%d%d",&u,&v); eg[i].v = v; eg[i].next = head[u]; head[u] = i; } memset(vis,0,sizeof(vis)); for(int i = 1; i <= n; ++i) { if(vis[i]) continue; tm = 0; tarjan(i); } memset(in,0,sizeof(in)); memset(can,0,sizeof(can)); int f = 0; for(int i = 1; i <= n; ++i) { for(int j = head[i]; j != -1; j = eg[j].next) { v = eg[j].v; //两个点不在同一个分支内 if(pre[v] != pre[i]) { in[i]++; } } //该点出度不为0 if(in[i]) { //该缩点出度不为0 can[pre[i]] = 1; } } int cnt = 0; for(int i = 1; i <= n; ++i) { //该点为分支根,同时该分支出度为0 if(pre[i] == i && can[i] == 0) { f = i; cnt++; } if(cnt > 1) break; } //出度为0的分支多余1个 if(cnt != 1) { puts("0"); } else { cnt = 0; //统计分支中的点数 for(int i = 1; i <= n; ++i) { if(pre[i] == f) cnt++; } printf("%d\n",cnt); } } return 0; }