P2341 【模板】强连通分量 / [HAOI2006]受欢迎的牛【Tarjan】

 P2341 【模板】强连通分量 / [HAOI2006]受欢迎的牛

这道题的题解

因为要找到所有的奶牛明星,奶牛明星就是所有奶牛都喜欢的那个奶牛!

首先,很明显的是:一个强连通分量中所有结点都相互喜欢!所以我们将图中所有的强连通分量缩聚成一个大结点!

那么,这些大结点中有指向关系。如果某个大结点的出度为0,那么说明所有奶牛都喜欢它,但是它没有喜欢的人!那么这个大结点就是明星奶牛的聚集地啊~

但是如果出度为0的大结点不止一个,也就是说这些出度为0的点都没有喜欢的人,所以不满足奶牛明星的条件!所以此时不存在奶牛明星!

直接上图解释一下!(一定是有向图)

(1)、

对于上面这个有向图的强连通分量很明显是下面的两个,所以此时的奶牛明星数量就是num[ 2 ] = 3

(2)、

而对于上面的这个有向图,它的强连通分量有3个,如下图。其中第2和第3个强连通分量都是出度为0,所以强连通分量3不喜欢强连通分量2,而强连通分量2也不喜欢强连通分量3,所以两个都不满足所有奶牛都喜欢它的条件!所以没有明星奶牛!


说下此题的核心算法:求强连通分量的Tarjan!

dfn[ ]:dfs序,每个结点的时间戳

low[ ]:一个强连通分量的所有结点都是相同low[ ],是所有结点中最小的时间戳。这个数组的存在是用来判断某个结点是不是一个强连通分量的根结点的!判断条件就是dfn[ i ] == low[ i ] 【个人认为这个数组理解了,剩下的都好说的!建议:想透我们要求的是强连通分量,并且想透low[ ]和dfn[ ]的关系(也就是上面黑体的那句话),根据栗子理解更快更好一些~】

stake[ ]:栈

inSta[ ]:结点是否在栈中的标记数组

cnnct:强连通分量的个数

belong[ ]:某个结点所属的强连通分量的标号

num[ ]:某个强连通分量所包含的结点个数

du[ ]:某个强连通分量的出度!【千万不要搞成了结点的出度,而且一定要判断一下儿子结点是否和它本身在同一个强连通分量中!】

Tarjan的参考博客(我觉得他讲解的挺好的,但是不怎么喜欢他贴的代码)


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

using namespace std;

const int maxN = 50004;

inline int read()
{
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') f = -f; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

struct EDGE{
    int adj, to;
    EDGE(int a = -1, int b= 0): adj(a), to(b) {}
}edge[maxN];
int head[maxN], cnt;

int dfn[maxN], low[maxN];
bool inSta[maxN];
int cnnct, num[maxN], belong[maxN];
int stack[maxN], tot;
int du[maxN];

void init()
{
    memset(head, -1, sizeof(head));
    memset(inSta, false, sizeof(inSta));
    memset(num, 0, sizeof(num));
    memset(du, 0, sizeof(du));
    memset(dfn, 0, sizeof(dfn));
    cnnct = cnt = tot = 0;
}

void add_edge(int u, int v)
{
    edge[cnt] = EDGE(head[u], v);
    head[u] = cnt ++ ;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ cnt;
    stack[++tot] = u; //入栈
    inSta[u] = true; //标记
    for(int i = head[u]; ~i; i = edge[i].adj)
    {
        int v = edge[i].to;
        if(!dfn[v]) //没有搜到过
        {
            tarjan(v);
            low[u] = min(low[u], low[v]); //回溯更新
        }
        else if(inSta[v])//在栈中
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(dfn[u] == low[u]) //是强连通分量的根结点,将整个强连通分量的结点出栈
    {
        ++ cnnct;
        int now;
        do{
            now = stack[tot--];
            inSta[now] = false;
            belong[now] = cnnct;
            ++ num[cnnct];
        }while(now != u);
    }
}

void getDu(int u)
{
    for(int i = head[u]; ~i; i = edge[i].adj)
        if(belong[edge[i].to] != belong[u])
            ++ du[belong[u]];
}

int main()
{
    int n, m;
    n = read(); m = read();
    init();
    for(int i = 0; i < m; ++ i)
    {
        int u, v;
        u = read(); v = read();
        add_edge(u, v);
    }
    cnt = 0;
    for(int i = 1; i <= n; ++ i )
        if(!dfn[i]) tarjan(i);
    for(int i = 1; i <= n; ++ i ) getDu(i);
    int zero = 0, ans;
    for(int i = 1; i <= cnnct; ++ i )
    {
        if(!du[i])
        {
            if(zero ++ ) break;
            else ans = num[i];
        }
    }
    if(zero & 1) printf("%d\n", ans);
    else printf("0\n");
    return 0;
}
/*
8 11
1 3
1 2
2 4
4 1
3 4
3 5
4 6
6 7
7 5
5 6
1 8

7 10
1 3
1 2
2 4
4 1
3 4
3 5
4 6
6 7
7 5
5 6
 */

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值