有向图强连通分量Tarjan

本文深入讲解了Tarjan算法,一种用于寻找有向图中强连通分量的有效方法。通过详细解释DFN与LOW数组的作用及计算过程,帮助读者理解算法原理。并以一道竞赛题目为例,展示了如何运用Tarjan算法解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

  所谓强连通,就是一个图中任意两点都是连通的,比如两个点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

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值