强连通分量

有向图强连通分量:在有向图中有一些点,这些点中如果能从A到B,那么一定从B能到达A,这些点组成的点数最多的子图是原图的一个强连通分量。 运用场合:有向图、有两两可达这种条件、往往通过把每个强连通分量缩点把原图化简成一棵树
scc.jpg
如图:

  • 1,2,3号点构成了一个强连通分量,它们可以互相到达
  • 4号点自己构成了一个强连通分量,5号点自己构成了一个强连通分量
  • 所以图中一共有3个强连通分量
  • sccno[1] = 1 sccno[2] = 1 sccno[3] = 1 sccno[4] = 2 sccno[3] = 3
  • sccno[i]代表第i个点所在的强连通分量的编号

模板代码:

void dfs(int u) { //从u点开始找一个强连通分量
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for (int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i];
        if (!pre[v]) {
            dfs(v);
            if (lowlink[u] > lowlink[v]) lowlink[u] = lowlink[v];
        } else if (!sccno[v]) {
            if (lowlink[u] > pre[v]) lowlink[u] = pre[v];
        }
    }
    if (lowlink[u] == pre[u]) {
        scc_cnt++;
        for(;;) {
            int x = S.top(); S.pop();
            sccno[x] = scc_cnt;
            if (x == u) break;
        }
    }
}
void find_scc(int n) { //n是点的总数,点的范围1-n
    dfs_clock = scc_cnt = 0;
    MEM(sccno); MEM(pre); MEM(ans);
    for (int i = 1; i <= n; i++) {
        if (!pre[i]) dfs(i);
    }
}

POJ 3180 The Cow Prom

有N头奶牛,和M个绳索。每个绳索从一头奶牛处发出,射向另一头奶牛。现在奶牛们想要跳舞,如果沿着射向它的绳索往回找,找到另一头牛,并如此找下去,最后能找到自己发出的绳索,那么这头奶牛就跳舞成功了,并且在寻找过程中,串起来的这些奶牛属于同一个集合。问最后这M个绳索连出了几个集合?
先明确什么是同一个集合,A->B->C->D->E->A这样A,B,C,D,E就是同一个集合,如果还有C->F->C,那么F也属于上面那个集合,所以这题就是求点数大于2的强连通分量的个数。
大白书的模板(Tarjan)中用了一个sccno[]数组,sccno[i]记录第i个点所属的强连通分量号然后我们只需要用一个cnt[]数组,记录每个块中点的个数,如果cnt[i] > 1,说明第i个块是符合条件的,答案数目加1,最后输出答案数。

#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
using namespace std;

#define MEM(a) memset(a, 0, sizeof(a))
const int maxv = 21000;
vector<int>G[maxv];
int pre[maxv], lowlink[maxv], sccno[maxv], ans[maxv], dfs_clock, scc_cnt;
stack<int>S;

void dfs(int u) {
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for (int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i];
        if (!pre[v]) {
            dfs(v);
            if (lowlink[u] > lowlink[v]) lowlink[u] = lowlink[v];
        } else if (!sccno[v]) {
            if (lowlink[u] > pre[v]) lowlink[u] = pre[v];
        }
    }
    if (lowlink[u] == pre[u]) {
        scc_cnt++;
        for(;;) {
            int x = S.top(); S.pop();
            sccno[x] = scc_cnt;
            if (x == u) break;
        }
    }
}
void find_scc(int n) {
    dfs_clock = scc_cnt = 0;
    MEM(sccno); MEM(pre); MEM(ans);
    for (int i = 1; i <= n; i++) {
        if (!pre[i]) dfs(i);
    }
}

int main() {
    int n, m, u, v;
    while (scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= n; i++) G[i].clear();
        for (int i = 0; i < m; i++) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
        }
        find_scc(n);
        for (int u = 1; u <= n; u++) {
            ans[sccno[u]]++;
        }
        int cnt = 0;
        for (int i = 1; i <= scc_cnt; i++)
            if (ans[i] > 1) cnt++;
        printf("%d\n", cnt);
    }
    return 0;
}

HDU 3816 The King’s Problem

有一个n个点m条边的有向图,把这个图分成几个区域,使得每个区域中的任意两点u, v要么u能到v,要么v能到u,求最少要分成几个区域

缩点得到有向的树,即求这棵树的最少路径覆盖(点数 – 二分图的最大匹配)

#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
using namespace std;

#define MEM(a) memset(a, 0, sizeof(a))
const int maxv = 15100;
vector<int>G[maxv], H[maxv];
int pre[maxv], lowlink[maxv], sccno[maxv], left[maxv], dfs_clock, scc_cnt;
bool vis[maxv];
stack<int>S;

void dfs(int u) {
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for (int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i];
        if (!pre[v]) {
            dfs(v);
            if (lowlink[u] > lowlink[v]) lowlink[u] = lowlink[v];
        } else if (!sccno[v]) {
            if (lowlink[u] > pre[v]) lowlink[u] = pre[v];
        }
    }
    if (lowlink[u] == pre[u]) {
        scc_cnt++;
        for(;;) {
            int x = S.top(); S.pop();
            sccno[x] = scc_cnt;
            if (x == u) break;
        }
    }
}

void find_scc(int n) {
    dfs_clock = scc_cnt = 0;
    MEM(sccno); MEM(pre);
    for (int i = 1; i <= n; i++) {
        if (!pre[i]) dfs(i);
    }
}

bool match(int u) {
    for (int i = 0; i <(int)H[u].size(); i++) {
        int v = H[u][i];
        if (!vis[v]) {
            vis[v] = 1;
            if (left[v] == -1 || match(left[v])) {
                left[v] = u;
                return true;
            }
        }
    }
    return false;
}

int main() {
    int n, m, u, v, t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            G[i].clear();
            H[i].clear();
        }
        for (int i = 0; i < m; i++) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
        }
        find_scc(n);
        for (int u = 1; u <= n; u++) {
            for (int i = 0; i < (int)G[u].size(); i++) {
                int v = G[u][i];
                if (sccno[u] != sccno[v])
                    H[sccno[u]].push_back(sccno[v]);
            }
        }
        int sum = 0;
        memset(left, -1, sizeof(left));
        for (int i = 1; i <= scc_cnt; i++) {
            MEM(vis);
            if (match(i)) sum++;
        }
        printf("%d\n", scc_cnt-sum);
    }
    return 0;
}

results matching ""

    No results matching ""