【详解】Popular Cows POJ - 2186 ⭐⭐⭐ 【强连通分量 缩点】(三种方法)

有N(N<=10000)头牛,每头牛都想成为most poluler的牛,给出M(M<=50000)个关系,如(1,2)代表1欢迎2,关系可以传递,但是不可以相互,即1欢迎2不代表2欢迎1,但是如果2也欢迎3那么1也欢迎3.
给出N,M和M个欢迎关系,求被所有牛都欢迎的牛的数量。

Input

第一行N,M
接下来M行A,B,表示A欢迎B

Output

一个数,表示答案

Examples

Sample Input
3 3
1 2
2 1
2 3

Sample Output
1

Hint




题意:

求和所有点都联通的点的数量

题解:

一道经典的强连通分量基础题, 一共有三种方法, 这里我一一实现

第一种方法: Korosaju的拓扑序

我们知道通过Korosaju算法可以求出缩点后SCC的拓扑序, 这样在拓扑序最后的点就一定是与所有点都联通, 这样我们算一下拓扑序最后一个点SCC的实际点的数目
记得再检测一下是否真正与所有点联通, 防止图不联通

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const int inf = 1<<30;
const LL maxn = 10010;

int N, M;
vector<int> G[maxn];
vector<int> rG[maxn];
vector<int> vs;
bool used[maxn];
int topo[maxn];
void Dfs(int u){
    used[u] = true;
    for(int v = 0; v < G[u].size(); ++v)
        if(!used[G[u][v]])
            Dfs(G[u][v]);
    vs.push_back(u);
}
void rDfs(int u, int k){
    used[u] = true;
    topo[u] = k;
    for(int v = 0; v < rG[u].size(); ++v)
        if(!used[rG[u][v]])
            rDfs(rG[u][v], k);

}
int scc(){
    ms(used, 0);
    for(int i = 1; i <= N; ++i)
        if(!used[i])
            Dfs(i);
    ms(used, 0);
    int k = 1;
    for(int i = vs.size()-1; i >= 0; --i)
        if(!used[vs[i]])
            rDfs(vs[i], k++);
    return k-1;
}

int main()
{
    int a, b;
    cin >> N >> M;
    for(int i = 1; i <= M; ++i){
        cin >> a >> b;
        G[a].push_back(b);
        rG[b].push_back(a); //构建反向图
    }
    int n = scc();
    int u = 1, ans = 0;
    for(int v = 1; v <= N; ++v)
        if(topo[v]==n)
            u = v, ++ans;
    ms(used, 0);
    rDfs(u, 1); //最后再搜一下, 防止图不连通
    for(int i = 1; i <= N; ++i)
        if(!used[i]){
            ans = 0;
            break;
        }
    cout << ans << endl;

	return 0;
}
第二种方法: Korosaju的出度表

因为要知道使用Korosaju算法更重要的是求出了各个点所属的SCC, 这样我们针对缩点后的SCC图建立出入度表, 可以想到在DAG上与所有点都联通的点一定出度为0, 且只能有一个, 多个的话就说明图不连通
最后统计一下原图上在该SCC中点的数目

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const int inf = 1<<30;
const LL maxn = 10010;

int N, M;
vector<int> G[maxn];
vector<int> rG[maxn];
vector<int> vs;
bool used[maxn];
int topo[maxn];
int in[maxn], out[maxn];

void Dfs(int u){
    used[u] = true;
    for(int v = 0; v < G[u].size(); ++v)
        if(!used[G[u][v]])
            Dfs(G[u][v]);
    vs.push_back(u);
}
void rDfs(int u, int k){
    used[u] = true;
    topo[u] = k;
    for(int v = 0; v < rG[u].size(); ++v)
        if(!used[rG[u][v]])
            rDfs(rG[u][v], k);

}
int scc(){
    ms(used, 0);
    for(int i = 1; i <= N; ++i)
        if(!used[i])
            Dfs(i);
    ms(used, 0);
    int k = 1;
    for(int i = vs.size()-1; i >= 0; --i)
        if(!used[vs[i]])
            rDfs(vs[i], k++);
    return k-1;
}

int main()
{
    int a, b;
    cin >> N >> M;
    for(int i = 1; i <= M; ++i){
        cin >> a >> b;
        G[a].push_back(b);
        rG[b].push_back(a); //构建反向图
    }
    int n = scc();

    int cnt = 0, tag, ans = 0;
    for(int u = 1; u <= N; ++u)
        for(int i = 0; i < G[u].size(); ++i){
            int v = G[u][i];
            if(topo[u] != topo[v])
                ++out[topo[u]], ++in[topo[v]];
        }
    for(int i = 1; i <= n; ++i)
        if(out[i]==0)
            ++cnt, tag = i;
    if(cnt!=1)
        cout << "0" << endl;
    else{
        for(int i = 1; i <= N; ++i)
            if(topo[i]==tag)
                ++ans;
        cout << ans << endl;
    }

	return 0;
}
第二种方法: Tarjan的出度表

Tarjan算法同样可以在O(N+M)的复杂度下求出所点后的SCC, 其他原理同上一个一样.

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const int inf = 1<<30;
const LL maxn = 50010;

int N, M, ecnt = 1, head[maxn];
struct node{
    int v, next;
}es[maxn];
void addEdge(int u, int v){
    es[ecnt].v = v, es[ecnt].next = head[u];
    head[u] = ecnt++;
}
int indx = 0, scc = 0;
bool ins[maxn];
int dfn[maxn], low[maxn], belong[maxn];
stack<int> s;
void tarjan(int u){
    int v;
    dfn[u] = low[u] = ++indx;
    s.push(u);
    ins[u] = true;
    for(int i = head[u]; i != -1; i = es[i].next){
        v = es[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])
            low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u]){
        ++scc;
        do{
            v = s.top();
            s.pop();
            ins[v] = false;
            belong[v] = scc;
        }while(u!=v);
    }
}
int out[maxn], in[maxn];
int main()
{
    int a, b;
    ms(head, -1);
    cin >> N >> M;
    for(int i = 1; i <= M; ++i){
        cin >> a >> b;
        addEdge(a, b);
    }
    for(int i = 1; i <= N; ++i){
        if(!dfn[i])
            tarjan(i);
    }

    for(int u = 1; u <= N; ++u){
        for(int i = head[u]; i != -1; i = es[i].next){
            int v = es[i].v;
            if(belong[u] != belong[v])
                ++out[belong[u]], ++in[belong[u]];
        }
    }
    int cnt = 0, ans = 0, tag;
    for(int i = 1; i <= scc; ++i)
        if(out[i]==0)
            ++cnt, tag = i;
    if(cnt!=1) cout << "0" << endl;
    else{
        for(int i = 1; i <= N; ++i)
            if(belong[i] == tag)
                ++ans;
        cout << ans << endl;;
    }

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值