Popular Cows POJ - 2186(图的强连通)(tarjan/kosaraju算法)

传送门

题意:每头牛都想成为牛群中的红人。给定N头牛的牛群和M个有序对(A,B)。(A,B)表示牛A认为牛B是红人。该关系具有传递性,所以如果牛A认为牛B是红人,牛B认为牛C是红人,那么牛A也认为牛C是红人。求被所有牛认为是红人的牛的总数。

题解:考虑以牛为顶点的有向图,对每个有序对(A,B)连一条从A到B的有向边。那么,被其他所有牛认为是红人的牛所对应的顶点,也就是从其他点都可达的顶点。虽然这可以通过从每个顶点出发搜索得到,但总的复杂度是O(NM),是不行的,必须考虑更为高效的算法,假设有两头牛A和B都被其他所有牛认为是红人,那么其所属的强连通分量内的所有牛都被其他所有牛认为是红人。由此,把图进行强连通分量分解后,至多有一个强连通分量满足题目的条件。

两种解法:kosareju / tarjan

一:kosaraju

按照此算法分解后,能够得到各个强连通分量拓扑排序后的顺序,唯一可以成为解的只有拓扑排序最后的强连通分量。所以在最后,只要检查这个强连通分量是否从所有顶点可达就好了。

附上代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>

using namespace std;

const int max_v=1e4+50;

int V,m;
vector<int>g[max_v];
vector<int>rg[max_v];
vector<int>vs;
bool used[max_v];
int cmp[max_v];

void add_edge(int from,int to)
{
    g[from].push_back(to);
    rg[to].push_back(from);
}

void dfs(int v)
{
    used[v]=true;
    for(int i=0;i<g[v].size();i++){
        if(!used[g[v][i]]){
            dfs(g[v][i]);
        }
    }
    vs.push_back(v);
}

void rdfs(int v,int k)
{
    used[v]=true;
    cmp[v]=k;
    for(int i=0;i<rg[v].size();i++){
        if(!used[rg[v][i]]){
            rdfs(rg[v][i],k);
        }
    }
}

int scc()
{
    memset(used,0,sizeof(used));
    vs.clear();
    for(int v=0;v<V;v++){
        if(!used[v]){
            dfs(v);
        }
    }
    memset(used,0,sizeof(used));
    int k=0;
    for(int i=vs.size()-1;i>=0;i--){
        if(!used[vs[i]]){
            rdfs(vs[i],k++);
        }
    }
    return k;
}

int main()
{
    scanf("%d%d",&V,&m);
    int u,v;
    for(int i=0;i<m;i++){
        scanf("%d%d",&u,&v);
        add_edge(u-1,v-1);
    }
    int n=scc();
    int num=0;
    for(v=0;v<V;v++){
        if(cmp[v]==n-1){
            num++;
            u=v;
        }
    }
    memset(used,0,sizeof(used));
    rdfs(u,0);
    for(int v=0;v<V;v++){
        if(!used[v]){
            num=0;
            break;
        }
    }
    printf("%d\n",num);
    return 0;
}

二:tarjan算法

思路:将n头奶牛的m个关系将会构建一个有向图,在图中强连通分量中的任意奶牛一定是被分量中的其他奶牛仰慕。所以问题就转化为在图中将强连通分量进行缩点,在形成的新图中,如果一个强连通分量集合的出度为零,说明这个集合被其他集合仰慕,而不仰慕其他的集合,所以如果在新图中集合出度为零的数目不大于1,则为出度为零集合中奶牛的数目,如果大于1,则出度为零集合之间没有仰慕关系的,所以结果为0.

附上代码:


#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>

using namespace std;

const int maxn=10050;

int n,m;
vector<int>g[maxn];
int temper,top,dfn[maxn],low[maxn],zhan[maxn],used[maxn];
int cnt,num[maxn];
vector<int>lt[maxn];
int d[maxn];

void tarjan(int u)
{
    dfn[u]=low[u]=++temper;
    used[u]=1;
    zhan[top++]=u;
    for(int i=0;i<g[u].size();i++){
        int temp=g[u][i];
        if(!dfn[temp]){
            tarjan(temp);
            low[u]=min(low[u],low[temp]);
        }else if(used[u]){
            low[u]=min(low[u],dfn[temp]);
        }
    }
    int j;
    if(dfn[u]==low[u]){
        cnt++;
        do{
           j=zhan[--top];
           used[j]=0;
           lt[cnt].push_back(j);
           num[j]=cnt;
        }while(u!=j);
    }
}

void solve()
{
    for(int i=0;i<n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    int u,v;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        g[u-1].push_back(v-1);
    }
    solve();
    for(int i=0;i<n;i++){
        for(int j=0;j<g[i].size();j++){
            int t=g[i][j];
            if(num[i]!=num[t]){
                d[num[i]]++;
            }
        }
    }
    int pos=-1;
    int ans=0;
    for(int i=1;i<=cnt;i++){
        if(d[i]==0){
            ans++;
            pos=i;
        }
    }
    if(ans==1){
        printf("%d\n",lt[ans].size());
    }else{
        printf("0\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/81625457
今日推荐