洛谷 - P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
对图进行缩点
答案即为出度为0的强连通分量包含点的数量
注意特判:如果缩点后的图有多个点出度为0,则没有任何一头牛是明星
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
// 定义常量:N为最大节点数(牛的数量),M为最大边数(喜欢关系)
const int N = 1e4 + 10, M = 5e4 + 10;
int n, m; // n表示牛的数量,m表示喜欢关系的数量
int e[M], ne[M], h[N], idx; // 邻接表存储图:e[]存储边的终点,ne[]存储下一条边的索引,h[]存储头节点,idx为边的计数器
int low[N], dfn[N], timestamp; // low[]存储节点的最低可达时间戳,dfn[]存储节点的发现时间戳,timestamp为时间戳计数器
int cnt; // 强连通分量(SCC)的数量
int stk[N], top; // 栈stk[]用于存储当前路径上的节点,top为栈顶指针
bool instk[N]; // 标记节点是否在栈中
int scc[N], size[N]; // scc[]存储节点所属的SCC编号,size[]存储每个SCC的节点数量
int outdegree[N]; // 存储缩点后每个SCC的出度
// 向邻接表中添加一条从a到b的边(表示a喜欢b)
void add(int a, int b) {
e[idx] = b; // 存储边的终点
ne[idx] = h[a]; // 下一条边指向当前a的头节点
h[a] = idx++; // 更新a的头节点为当前边的索引,边计数器加1
}
// Tarjan算法:用于寻找强连通分量
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp; // 初始化发现时间和最低可达时间为当前时间戳,时间戳加1
stk[++top] = u; // 将当前节点入栈
instk[u] = true; // 标记当前节点在栈中
// 遍历当前节点的所有邻接边
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i]; // 获取邻接节点v
if (!dfn[v]) { // 如果v未被访问过
tarjan(v); // 递归访问v
// 更新当前节点的最低可达时间为其自身和v的最低可达时间的最小值
low[u] = min(low[u], low[v]);
} else if (instk[v]) { // 如果v已被访问且在栈中(说明在当前路径上)
// 更新当前节点的最低可达时间为其自身和v的发现时间的最小值
low[u] = min(low[u], dfn[v]);
}
}
// 如果当前节点是其所在强连通分量的根节点(low[u] == dfn[u])
if (low[u] == dfn[u]) {
cnt++; // 强连通分量数量加1
int v;
// 将栈中从当前节点开始的所有节点弹出,组成一个强连通分量
do {
v = stk[top--]; // 弹出栈顶节点
instk[v] = false; // 标记节点不在栈中
scc[v] = cnt; // 记录节点v所属的强连通分量编号
size[cnt]++; // 该强连通分量的节点数量加1
} while (u != v); // 直到弹出当前节点u为止
}
}
int main() {
memset(h, -1, sizeof h); // 初始化邻接表头节点为-1(表示无邻接边)
scanf("%d%d", &n, &m); // 读取牛的数量和喜欢关系的数量
for (int i = 1, a, b; i <= m; i++) { // 读取每条喜欢关系(a喜欢b)并添加到邻接表
scanf("%d%d", &a, &b);
add(a, b);
}
// 对所有未访问的节点执行Tarjan算法,寻找所有强连通分量
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
// 计算缩点后每个SCC的出度
for (int u = 1; u <= n; u++) {
// 遍历u的所有邻接边
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i]; // 邻接节点v
// 如果u和v属于不同的SCC,则u所在的SCC的出度加1
if (scc[u] != scc[v])
outdegree[scc[u]]++;
}
}
int zeros = 0, ans = 0; // zeros统计出度为0的SCC数量,ans存储结果
// 遍历所有SCC,统计出度为0的SCC
for (int i = 1; i <= cnt; i++) {
if (outdegree[i] == 0) {
zeros++; // 出度为0的SCC数量加1
ans = size[i]; // 记录该SCC的节点数量
}
}
// 如果出度为0的SCC有多个,则没有牛被所有牛喜欢,答案为0
// 否则,答案为该SCC的节点数量
if (zeros > 1)
ans = 0;
printf("%d\n", ans); // 输出答案
return 0;
}