对Tarjan的理解(详) poj 2186 缩点+Tarjan

题目链接:http://poj.org/problem?id=2186

一、多日不写图论,链式前向星都写错


        for(i=0;i<m;i++)
        {
            scanf("%d%d",&u,&v);
            addEdge(u-1,v-1,i);/*此处如果addEdge(u,v,i),后面的addEdge(u-1,v-1,i);就会有问题,因为Tarjan(0)无意义*/
        }

        for(i=0;i<n;i++)
            if(dfn[i]==-1)Tarjan(i);

二、这个Tarjan作为模板不错,我做为一个理解能力差的人,这里写一个对Tarjan的理解,当然不是完整的证明,希望对像我一样在学Tarjan时比较费力的朋友一点帮助吧:

先解释名词:dfn[v]:节点v的深度优先数,就是DFS遍历的深度,从1开始。

low[v]:v和v的子孙的最小值

这里有一点:想想low[u]的值什么时候会被改变?
DFS初始的时候low[u]被初始化为深度优先数,它被改变必然是子孙的low比它的low小,

在想想,既然子孙是通过DFS遍历,并且是后来被遍历的,那么深度优先数应该比low[u]大啊,怎么会小呢?

答案是:因为有回边。(这个看图论的书可以看明白什么叫回边,交叉边,很多资料避开了,但我觉得认真学习算法的话,这些还是学学吧)

       low[u]的求法:

     low[u]=Min{dfn[u],Min{low[w]|wu的一个子女},Min{dfn[v]|vu邻接,(u,v)是一条回边}}

      区分wu的子女还是(w,u)是回边的方法:dfs的时候,若与当前u邻接的点w已经被访问过,则(w,u)是回边,否则w是u的子女

我觉得low的值应该这样理解:如low[u]=depth,意味着从low出发,可以到达的最低的深度为depth,这样想的话,Tarjan就可以有个模糊的认识了。某个点u,low[u]==dfn[u],从u出发,可以再回到u,假设从u到u的路径为u,v1,v2,v3,v4,v5,v6,u,那么从v2出发,可以通过v2,v3...v5的路径到v5,也可以从v5出发,通过v5,v6,u,v1,v2到v2,试试吧,这个从u到u的任意两个点i,j,都有从i到j的路和从j到i的路,这不就是强连通分量的定义?


有了这个逻辑,再去看算法模板应该很容易了。

三、多嘴一句,谈谈对判断回边生成树的边的做法:一种就是上面写的区分wu的子女还是(w,u)是回边的方法:dfs的时候,若与当前u邻接的点w已经被访问过,则(w,u)是回边,否则w是u的子女,另一种就是这个代码模板的做法:其实异曲同工,

min1=min(min1,low[v]);
if(min1<low[u]){low[u]=min1;return;}如果发生了min1<low[u],说明有从u的子孙到u的祖先的回边,则绝不可能low[u]==low[v],所以此处return

另一种就是跟Tarjan 的伪码写法一样了,两种写法都一样其实,我觉得就是stack做法不同,stack用STL的话,查找某个元素是不是在栈里就很麻烦,如果用数组表示stack就很容易查找某个元素是不是在栈里

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<cstdlib>
using namespace std;
#define M 50002
#define N 10002

stack<int>st;
int head[N],id[N],low[N],dfn[N];
int scnt,cnt,n,m;
//cnt记录访问的次序

struct node{
  int to,next;
  int id;
}edge[M];

void addEdge(int u,int v,int k)
{
    edge[k].to=v;
    edge[k].next=head[u];
    head[u]=k;
}

void Tarjan(int u)
{
    int k,v,t;
    int min1=dfn[u]=low[u]=cnt++;
    st.push(u);
    for(k=head[u];k!=-1;k=edge[k].next)
    {
        v=edge[k].to;
        if(dfn[v]==-1)Tarjan(v);
        min1=min(min1,low[v]);
     }
    if(min1<low[u]){low[u]=min1;return;}
    do
    {
        id[t=st.top()]=scnt;//缩点操作一
        low[t]=n;//这一句别忘了,否则这个节点会被多次遍历
        st.pop();
    }while(t!=u);
    scnt++;
}


用的时候要这么写:

memset(head,-1,sizeof(head));
memset(dfn,-1,sizeof(dfn));      
for(i=0;i<n;i++)
      if(dfn[i]==-1)Tarjan(i);



对于poj 2186,牛崇拜对方,崇拜可以传递,那么将强联通分量缩点,找出出度为0的点,如果只有一个出度为0的点,那么这个点(也可能是经过缩点的点),包含的点的个数就是answer。如果出度为0的点 多于1个,这些出度为0的点之间就没有崇拜关系,肯定输出0了


 不多言了,贴代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<cstdlib>
using namespace std;
#define M 50002
#define N 10002

stack<int>st;
int head[N],id[N],low[N],dfn[N],in[N],out[N];
int scnt,cnt,n,m,n2;
//cnt记录访问的次序,n2记录

struct node{
  int to,next;
  int id;
}edge[M];

void addEdge(int u,int v,int k)
{
    edge[k].to=v;
    edge[k].next=head[u];
    head[u]=k;
}

void Tarjan(int u)
{
    int k,v,t;
    int min1=dfn[u]=low[u]=cnt++;
    st.push(u);
    for(k=head[u];k!=-1;k=edge[k].next)
    {
        v=edge[k].to;
        if(dfn[v]==-1)Tarjan(v);
        min1=min(min1,low[v]);
     }
    if(min1<low[u]){low[u]=min1;return;}/*这里我写成if(min1<low[v]){low[u]=min1;return;}  WA了无数次*/
    do
    {
        id[t=st.top()]=scnt;//缩点操作一
        low[t]=n;
        out[scnt]++;

        st.pop();
    }while(t!=u);
    scnt++;
}

int main()
{
    int i,j,u,v,ans,flag;

    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,-1,sizeof(head));
        memset(dfn,-1,sizeof(dfn));
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));

        scnt=cnt=ans=0;

        for(i=0;i<m;i++)
        {
            scanf("%d%d",&u,&v);
            addEdge(u-1,v-1,i);/*此处如果addEdge(u,v,i),后面的addEdge(u-1,v-1,i);就会有问题,因为Tarjan(0)无意义*/
        }

        for(i=0;i<n;i++)
            if(dfn[i]==-1)Tarjan(i);

        for(i=0;i<n;i++)
            for(j=head[i];j!=-1;j=edge[j].next)
            {
                int a=id[i],b=id[edge[j].to];
                 if(a!=b)in[id[i]]++;
            }
        for(i=0;i<scnt;i++)
            if(!in[i])
            {
                ans++;
                flag=i;
            }
        if(ans==1)printf("%d\n",out[flag]);
        else printf("0\n");
    }

    return 0;
}



你可能感兴趣的:(对Tarjan的理解(详) poj 2186 缩点+Tarjan)