受欢迎的牛(有向图的强连通分量)

题目:https://www.acwing.com/problem/content/1176/
tarjan:O(n+m),,,这个tarjan算法,除了求强连通分量(合并完的环),还可以顺便求图中所有的环的数量(不是合并完的环),自己测的,没有证明,代码中的sss就是。

题意:如果A–》B,B–》C,那么A–》C,问在这个有向图中有多少个这样的A,除了A个点外的所有点都能有路走到A。

题解:暴力做法,就是枚举每一个点,看一下其他所有点是否都能到这个点,这个时间复杂度是n方级别的,肯定不行。
如果这个图有拓扑序列的话,那么按照这个拓扑序列走一遍,看看有一个出度为0的点,如果有一个出度为0的点,那么其它所有点就一定能走到这个点,但是这个图可能有环,所以它不一定有拓扑序列。
但是又可以发现,在这个题中,一个环中所有点是任意到达的,所以如果其它所有点都能到这个环,那么这个环中的所有点都是题目中要求的点,而且在这个环中,如果其中一个点可以走到环外的一个点,那么环中其它所有点也可以走到那个点,所以就可以把一个环看成一个点,把所有的环都压缩成一个点,那么就可以这个图就有拓扑序列了(当然,可能有环套环(最外层有个大环,里面的一些点又有一些小环)、环连环(俩个环通过都包括一个点)的情况,会把这些环一起都压缩成一个点),这样就可以做这个题了,而求一个图中的环的数量(这里的环的意义是环连环、环套环算是一个环,其实就是强连通分量),即求强连通分量的数量,用tarjan做法。

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
//#include <unordered_map>
//#include <unordered_set>
//#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
    char ch = getchar();
    int s = 0, w = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0', ch = getchar();
    }
    return s * w;
}
const int N = 10010, M = 50010;
//int sss=0;
int n, m;
int h[N], e[M], ne[M], idx;//存图
int dfn[N], low[N], timestamp;//low记录这个点可以走到的最早时间点,dfn记录这个点的时间点,timestamp是时间戳
int stk[N], top;  //栈
bool in_stk[N]; //判断是否在栈里面
int id[N], scc_cnt, id_size[N];  //id是这个数在第几个连通块,scc_cnt是总共有几个连通块,id_size是这个这个连通块的数量
int dout[N];   //记录出度,,上面的尾tarjan板子需要用的,这个数组是处理这个题的
void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void tarjan(int u) {
    dfn[u] = low[u] = ++timestamp;//记录本点的时间戳
    stk[++top] = u, in_stk[u] = true;//加入栈
    for (int i = h[u]; i != -1; i = ne[i]) { //找它连的所有边
        int j = e[i];
        if (!dfn[j]) {    //判断它是否走过,如果没走过就往下走
            tarjan(j);
            low[u] = min(low[u], low[j]); //找它能走的最早的点
        } else if (in_stk[j])   //如果它在栈里面,就说明这个点可以走到我这个点,我这个点也可以走到它,所以取最小
            low[u] = min(low[u], low[j]);
            //在这个上面这个分号前面加一个sss++
    }
    if (dfn[u] == low[u]) {//如果本点的时间点就是它能走到的最早的时间点,证明它要么是没有环,要么是环中最早出现的那个点
        int y;
        ++scc_cnt;//更新增加连通块,找这之间所有的点,这些都是在这个连通块中
        do {
            y = stk[top--];
            in_stk[y] = false;
            id[y] = scc_cnt;//更新这个点指向的连通块
            id_size[scc_cnt]++;//给这个连通块增加规模
        } while (y != u);
    }
}
signed main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof(h));
    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);//加入边
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) tarjan(i); //跑tarjan
    }
    for (int i = 1; i <= n; i++) {//枚举每一条边,进行计算每个连通块的出度
        for (int j = h[i]; ~j; j = ne[j]) {
            int k = e[j];
            int a = id[i], b = id[k];//找到这俩个点,看是否在同一个连通块中
            if (a != b) dout[a]++;//不在的话,因为是有向图,a这个连通块指向b这个连通块就多一条边
        }
    }
    int zeros = 0, sum = 0;  //如果只有一个连通块的出度为0,代表这个连通块之外的所有点都能走到它
                            //如果有俩个及以上连通块出度为0,就代表本题的答案是0
    for (int i = 1; i <= scc_cnt; i++) {
        if (!dout[i]) {
            zeros++;       //记录连通出度为0的数量
            sum += id_size[i];//加上连通块里面点的数量
            if (zeros > 1) {
                sum=0;
                break;
            }
        }
    }
    printf("%d\n", sum);
    return 0;
}


版权声明:本文为Are_you_ready原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。