题意
给 n 头牛和 m 个有序对(A, B),表示牛 A 认为牛 B 是受欢迎的。该关系具有传递性。求被其他所有牛认为受欢迎的牛的数量。
思路
满足条件的牛必在同一个强连通分量中,并且只可能存在于拓扑序最靠后的那个连通分量中。所以有如下做法:
1。两遍 dfs 求连通分量,同时保存各节点对应的拓扑序号。
2。遍历 n 个点求出最后一个连通分量的节点数,并任找一个代表节点。
3。从代表节点出发, 利用反向图,检验该节点是不是对 n 个节点都可达。
链接
http://poj.org/problem?id=2186
代码
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 1e4 + 10;
int n, m;
vector<int> G[maxn]; //图
vector<int> rG[maxn]; //反向图
vector<int> vs; //节点的有序数组
bool vis[maxn]; //访问标记
int cmp[maxn]; //节点所属的连通分量
void add(int x, int y){
G[x].push_back(y);
rG[y].push_back(x);
}
void dfs(int v){
vis[v] = true;
for(int i = 0; i < G[v].size(); ++i){
if(!vis[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v, int k){
vis[v] = true;
cmp[v] = k;
for(int i = 0; i < rG[v].size(); ++i){
if(!vis[rG[v][i]]) rdfs(rG[v][i], k);
}
}
int scc(){
memset(vis, false, sizeof vis); //第一遍dfs标序号(这里用有序数组代替)
vs.clear();
for(int i = 0; i < n; ++i){
if(!vis[i]) dfs(i);
}
int k = 0; //第二遍反向dfs分连通分量
memset(vis, false, sizeof vis);
for(int i = vs.size()-1; i >= 0; --i){
if(!vis[vs[i]]) rdfs(vs[i], k++);
}
return k;
}
int solve(){
int last = scc(); //求强连通分量,返回连通分量个数
int v = 0, num = 0; //最后一个连通分量节点数,代表节点
for(int i = 0; i < n; ++i){
if(cmp[i] == last - 1){
v = i, ++num;
}
}
memset(vis, false, sizeof vis); //一次dfs验证可达性
rdfs(v, 0);
for(int i = 0; i < n; ++i){
if(!vis[i]) return 0;
}
return num;
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 0; i < m; ++i){
int x, y;
scanf("%d %d", &x, &y);
add(x-1, y-1); //加边
}
printf("%d\n", solve());
return 0;
}