有向图强连通分量Tarjan

  这几天填鸭赛让人很无语啊。。搞一些听都没听过是什么算法的题。。

  所谓强连通,就是一个图中任意两点都是连通的,比如两个点a,b,既要有a到b的路径,又要有b到a的路径。Tarjan算法是用来算强连通分量的。

  Tarjan算法有两个关键的数组,一个是DFN,记录结点的搜索时间编号,也就是第几个搜索到这个点的。还有一个是LOW,记录这个点能够搜索到的时间编号最小的栈中结点。每次搜索一个点时把这个点加到栈中。当一个点的LOW和DFN相等时,以这个点为根的子树属于一个强连通分量(因为如果不相等,两个点都在栈中,显然可以通过LOW的点可以搜索到DFN这个点,又可以从DFN这个点回溯到LOW的点,这个点必然不是根)。然后就把这个强连通分量都退栈。

Low(u)=Min
{ DFN(u), Low(v),(u,v)为树枝边,u为v的父节点 DFN(v),(u,v)为指向栈中节点的后向边(非横叉边) }
void Tarjan(int u)
{
    int p,v;
    DFN[u]=LOW[u]=++time;
    instack[u]=true;
    stack[++top]=u;
    for(p=head[u]; p!=-1; p=e[p].next)
    {
        v=e[p].v;
        if(!DFN[v])
        {
            Tarjan(v);
            if(LOW[v]<LOW[u])
                LOW[u]=LOW[v];
        }
        else if(instack[v]&&DFN[v]<LOW[u])
            LOW[u]=DFN[v];
    }
    if(DFN[u]==LOW[u])
    {
        cnt++;
        do
        {
            v=stack[top--];
            instack[v]=false;
            Belong[v]=cnt;
        }
        while(v!=u);
    }
}
  Belong数组是记录当前结点属于哪一个强连通分量,到最后cnt就是强连通分量的个数。

  填鸭赛是这么个题

B - Popular Cows
Time Limit:2000MS    Memory Limit:65536KB    64bit IO Format:%I64d & %I64u
Submit Status Practice POJ 2186

Description

Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.

Input

* Line 1: Two space-separated integers, N and M

* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.

Output

* Line 1: A single integer that is the number of cows who are considered popular by every other cow.

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

  这道题还要缩点,把一个强连通分量缩成一个点,最后如果只有一个点没有出度,那么这个强连通分量包含元素的个数就是答案。判断一个点有没有出度,是看那个点里的元素连接的元素在不在一个Belong里,如果不在,就有出度。若只有一个点没有出度,记下这个点的Belong,再去找有多少个点的Belong等于它。

  写这个的时候看网上有些用的动态分配地址的链表,有些用的什么vector。。不会=。= 不知道为什么一看到链表就觉得不会写,不过好在慢慢写一下还是写出了数组的链表。。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<set>
#define INF 0x3f3f3f3f
#define MAXSTATE 2000007
using namespace std;
struct edge
{
    int v,next;
} e[50010];
int cnt,top,time,head[10010],LOW[10010],DFN[10010],stack[10010],Belong[10010],de[10010];
bool instack[10010];
void Tarjan(int u)
{
    int p,v;
    DFN[u]=LOW[u]=++time;
    instack[u]=true;
    stack[++top]=u;
    for(p=head[u]; p!=-1; p=e[p].next)
    {
        v=e[p].v;
        if(!DFN[v])
        {
            Tarjan(v);
            if(LOW[v]<LOW[u])
                LOW[u]=LOW[v];
        }
        else if(instack[v]&&DFN[v]<LOW[u])
            LOW[u]=DFN[v];
    }
    if(DFN[u]==LOW[u])
    {
        cnt++;
        do
        {
            v=stack[top--];
            instack[v]=false;
            Belong[v]=cnt;
        }
        while(v!=u);
    }
}
int N,M;
int main()
{
    freopen("in.txt","r",stdin);
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        int i,u,v,p,tot=0;
        memset(head,-1,sizeof(head));
        memset(DFN,0,sizeof(DFN));
        memset(de,0,sizeof(de));
        memset(instack,false,sizeof(instack));
        while(M--)
        {
            scanf("%d%d",&u,&v);
            e[tot].v=v;
            e[tot].next=head[u];
            head[u]=tot++;
        }
        top=cnt=time=0;
        for(i=1; i<=N; i++)
        {
            if(!DFN[i]) Tarjan(i);
        }
        //下面是有出度的缩点都标记
        for(i=1; i<=N; i++)
            for(p=head[i]; p!=-1; p=e[p].next)
            {
                if(Belong[i]!=Belong[e[p].v])
                {
                    de[Belong[i]]=1;
                    break;
                }
            }
        int c=0,s,ans=0;
        for(i=1; i<=cnt; i++)
            if(!de[i])
            {
                c++;
                s=i;  //s是没有出度的缩点的编号
            }
        if(c>1) printf("0\n");
        //只有一个缩点没有出度的,答案就是这个点所包含的元素个数
        else
        {
            for(i=1; i<=N; i++)
                if(Belong[i]==s) ans++;
            printf("%d\n",ans);
        }
    }
    return 0;
}


你可能感兴趣的:(有向图强连通分量Tarjan)