强连通分量——tarjan ->缩点

对于一些题目,我们找出强连通分量后,就会变得非常简单=v=+

首先介绍强连通:
对于一个有向图,每一对点(x,y)都可以相互到达,则称之为强连通图。
而一个有向图中的极大强连通子图,就称为强连通分量
(注:极大的意思就是说不能再往这个子图中添加点,即当前情况下的最大子图

(强连通都是:环,环套环,环套环套环……

接下来就是求出强连通分量:这里只介绍tarjan算法

定义时间戳dfn[i]表示i是第几个被访问到的节点。
定义返祖数组low[i]表示本块强连通分量中最早被访问的节点的时间
进行深搜,把路径上所有的点压入一个栈中。
那么什么时候弹出?
当一个强连通分量中所有点都被遍历的时候。一个最显而易见的条件:回溯到强连通分量最早被访问的节点。
根据low和dfn的定义,我们可以知道当low[x]==dfn[x]时,我们得到了一个包含x的强连通分量,此时就可以把x到栈顶的所有元素弹出并记录。

那么low[x]如何更新?
① 子节点回溯得到
② 访问到一个已被访问且未被弹出的点

tarjan-dfs部分

inline void dfs(int x)
{
    dfn[x]=low[x]=++index;
    sta[++top]=x;
    ins[x]=1;
    for(int i=f[x];i;i=next[i])
    {
        int t=poi[i];
        if(!dfn[t])    {    dfs(t);low[x]=min(low[x],low[t]);}
        else
        if(ins[t])
            low[x]=min(low[x],low[t]);
    }
    if(dfn[x]==low[x])
    {
        while(1)
        {
            cnt++;
            int t=sta[top];
            top--;
            ins[t]=0;
            lin[t]=cnt;
              XXXXXXXXX(依题目决定)
            if(t==x)    break;
        }
    }
}

所谓tarjan缩点,就是把整个强连通分量当作一个点来进行处理。
显然得到一个性质,缩点后的图不存在环。
下面以 bzoj1051/luogu2341 HAOI2006 受欢迎的牛为例
传送门

这道题的大意就是说有多少头牛被其他所有的牛喜欢,喜欢具有传递性。
乍一看。。。floyd传递闭包?
再一看数据- -
行吧,算我输

经过观察可以发现,我们如果把他们的关系绘成图有以下性质
① 一个强连通子图中所有奶牛都相互喜欢
② 若强连通子图A中有奶牛喜欢强连通子图B中的奶牛,则所有A中奶牛都喜欢所有B中奶牛

那么仔细思考一下,如果我们通过tarjan缩点,把整个图变成有向无环图,是不是就成了一道水题?

因为无环,所有要被所有奶牛崇拜,则这个强连通分量不会有边连到其他的强连通分量,否则就会构成环,即出度为0。
朴素的想法,如果一个强连通分量的入度=分量总数-1,并且出度=0,则满足条件。
但是如果动下脑子,,就可以想到一个巧妙的办法:
当出度为0的点>1个,则 入度=分量总数-1 条件绝对不成立
答案为0
如果出度为0的点=0个 则 出度=0 条件绝对不成立
答案为0
那么显然,如果答案要不为0,出度为0的点=1个!
答案即为这个点的权值(即缩成这个点的强连通分量所包含节点的数量)
CODE:

#include
#include
#include
#include
using namespace std;
bool ins[10001];
int index=0,top=0,cnt=0,n,m;
int sta[10001],dfn[10001],low[10001],poi[100001],next[100001],f[10001],lin[10001],x[50001],y[50001],num[10001],vis[10001],cant[10001];
inline void add(int x,int y)
{
    poi[++cnt]=y;next[cnt]=f[x];f[x]=cnt;
}
inline void dfs(int x)
{
    dfn[x]=low[x]=++index;
    sta[++top]=x;
    ins[x]=1;
    for(int i=f[x];i;i=next[i])
    {
        int t=poi[i];
        if(!dfn[t])    {    dfs(t);low[x]=min(low[x],low[t]);}
        else
        if(ins[x])
            low[x]=min(low[x],low[t]);
    }
    if(dfn[x]==low[x])
    {
        while(1)
        {
            int t=sta[top];
            top--;
            ins[t]=0;
            lin[t]=x;
            num[x]++;
            vis[x]=1;
            if(t==x)    break;
        }
    }
}
inline void out()
{
    for(int i=1;i<=m;i++)
    if(lin[x[i]]!=lin[y[i]])    cant[lin[x[i]]]=1;
    int hh=0,tx=-1;
    for(int i=1;i<=n;i++)
        if(!cant[i]&&vis[i])
        {
            tx=i;
            hh++;
            if(hh==2)    
            {
                printf("0\n");
                return;
            }
        }
    if(tx==-1)    {printf("0\n");return;}
    printf("%d\n",num[tx]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        add(x[i],y[i]);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])    dfs(i);
    }
    out();
}

你可能感兴趣的:(算法)