tarjan在acm里的应用

转载请注明原blog地址( http://blog.csdn.net/stl112514
学习了一下tarjan在acm里面的应用,首先仰慕下Robert Tarjan菊苣,摸完之后呢,写个总结。
据我所知,主要是用于以下三点:1、有向图中求极大强连通分量(无向图就拆成有向图处理);2、无向图中求桥(也就是割边,当然,顺便求下割点);3、无向图中求极大双连通分量(好像有一种说法说分为点连通分量和边连通分量,我感觉都是一个东西)。


据我所知,tarjan能够在O(n)的时间复杂度内求出上面提到的三个东西,竟然只是依托了两个辅助数组,实在是很神奇,不愧是上古菊苣啊。也就是,dfn:记录点被访问的时间戳;low:记录点直接或者间接连通的点的最小的dfn(在有向图中要用一个额外的栈把点区分开来,至于为什么,等下讨论),当然,这个是越小越好,我才疏学浅并不知道tarjan是怎么想出这种办法的,但是经过实践是非常有效的,理论上的证明我并不会。


接下来,我们来讨论怎么使用这两个数组,
1、有向图中求极大强连通分量:
把所有搜索树中的点都丢到栈里,把dfn的值赋值给low,再用直接连通的点更新某点的low,分两种情况,假设当前访问点u,且v是u的一个直接连通点,1、若v没被访问,则先访问,然后low[u]=min(low[u],low[v]);2、若v在栈里,则low[u]=min(low[u],dfn[v]),这是出现环时的特判,至于为啥这样,并不知道;其实还有情况3、v被访问,且不在栈里,则无视,因为这说明v在另一个极大强连通分量里,u不该去打扰。最后当出现low[u]==dfn[u]时,则说明以u为根的搜索树上的点构成了一个极大强连通分量,为啥?因为找不到比dfn[u]更小的dfn了,是不是刚好走完了一个极大强连通分量?这是十分巧妙的。


2、无向图中求桥:
当low[v]>dfn[u]时,边(u,v)就是桥,因为v无论如何也找不到不大于dfn[u]的dfn时,则说明v在另一个极大双连通分量内。为了让上述结论有用,我们规定一旦走了(u,v)就不再走回去,当然若有重边的情况就这样说,一条边只能走一次。


3、无向图中求极大双连通分量:
在求桥的基础上,当出现low[u]==dfn[u]时,以u为根的搜索树上的点构成了一个极大双连通分量,也就是综合1跟2的结论。


就这样,大概说了说理论知识,下面分别是三个题对应上述三种情况,结合下代码很快就能掌握了


hdu1827
http://acm.hdu.edu.cn/showproblem.php?pid=1827

中文题,做法是tarjan求出极大强连通分量然后缩点,再从重新建图,然后考虑度为零的点是一定要打电话的,就枚举巴拉巴拉什么的吧。

#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
int tail,cnt,num;
int dfn[1010],low[1010],belong[1010],head[1010],w[1010],mn[1010];
bool isin[1010],vis[1010];
stack<int>sk;
struct Edge
{
    int from,to,next;
}edge[4010];
void add(int from,int to)
{
    edge[tail].from=from;
    edge[tail].to=to;
    edge[tail].next=head[from];
    head[from]=tail++;
}
void dfs(int from)
{
    isin[from]=1;
    sk.push(from);
    dfn[from]=low[from]=++num;
    for(int i=head[from];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!dfn[to])
        {
            dfs(to);
            low[from]=min(low[from],low[to]);
        }
        else if(isin[to])
            low[from]=min(low[from],dfn[to]);
    }
    if(low[from]==dfn[from])
    {
        mn[++cnt]=w[from];
        while(1)
        {
            int t=sk.top();
            sk.pop();
            belong[t]=cnt;
            isin[t]=0;
            mn[cnt]=min(mn[cnt],w[t]);
            if(t==from)
                break;
        }
    }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",w+i);
        tail=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
        }
        num=cnt=0;
        memset(dfn,0,sizeof(dfn));
        for(int i=1;i<=n;i++)
            if(!dfn[i])
                dfs(i);
        memset(vis,0,sizeof(vis));
        for(int i=0;i<m;i++)
            if(belong[edge[i].from]!=belong[edge[i].to])
                vis[belong[edge[i].to]]=1;
        int ans1=0,ans2=0;
        for(int i=1;i<=cnt;i++)
            if(!vis[i])
            {
                ans1++;
                ans2+=mn[i];
            }
        printf("%d %d\n",ans1,ans2);
    }
}
hdu4738
http://acm.hdu.edu.cn/showproblem.php?pid=4738
题意:所有边都有个权值,求权值最小的桥。
特判:如果给出不止一个连通图,输出0;如果最小的桥为0,输出1。

#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
int tail,num,ans;
int dfn[1010],low[1010],head[1010];
struct Edge
{
    int id,val,to,next;
}edge[2000000];
void add(int from,int to,int id,int val)
{
    edge[tail].id=id;
    edge[tail].val=val;
    edge[tail].to=to;
    edge[tail].next=head[from];
    head[from]=tail++;
}
void dfs(int from,int fa)
{
    dfn[from]=low[from]=++num;
    for(int i=head[from];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(edge[i].id==fa)
            continue;
        if(!dfn[to])
        {
            dfs(to,edge[i].id);
            low[from]=min(low[from],low[to]);
            if(low[to]>dfn[from])
                ans=min(ans,edge[i].val);
        }
        else
            low[from]=min(low[from],dfn[to]);
    }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)
            return 0;
        tail=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,i,c);
            add(b,a,i,c);
        }
        int sum=0;
        ans=INT_MAX;
        num=0;
        memset(dfn,0,sizeof(dfn));
        for(int i=1;i<=n;i++)
            if(!dfn[i])
            {
                dfs(i,-1);
                sum++;
            }
        printf("%d\n",sum>1?0:ans==INT_MAX?-1:ans==0?1:ans);
    }
}

hdu4612
http://acm.hdu.edu.cn/showproblem.php?pid=4612
题意:随意加一条边,让图的桥的数量最小。
做法:双连通分量缩点,重新建图,形成一棵树,求出树的直径,双连通分量的数量减去直径上的点就是答案。要开下栈,用C++交。

#pragma comment(linker,"/STACK:102400000,102400000")
#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
struct Edge
{
    int id,u,v,next;
}edge[4000010];
int head[200010],tail;
void add(int u,int v,int id)
{
    edge[tail].u=u;
    edge[tail].v=v;
    edge[tail].id=id;
    edge[tail].next=head[u];
    head[u]=tail++;
}
stack<int>sk;
int dfn[200010],low[200010],belong[200010],cnt,num;
void dfs(int u,int fa)
{
    sk.push(u);
    dfn[u]=low[u]=++cnt;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        if(edge[i].id==fa)
            continue;
        int v=edge[i].v;
        if(!dfn[v])
        {
            dfs(v,edge[i].id);
            low[u]=min(low[u],low[v]);
        }
        else
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        num++;
        while(1)
        {
            int t=sk.top();
            sk.pop();
            belong[t]=num;
            if(t==u)
                break;
        }
    }
}
int mx,node,dp[200010];
void dfs(int u)
{
    if(dp[u]>mx)
    {
        mx=dp[u];
        node=u;
    }
    for(int i=head[u];i!=-1;i=edge[i].next)
        if(!dp[edge[i].v])
        {
            dp[edge[i].v]=dp[u]+1;
            dfs(edge[i].v);
        }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)
            return 0;
        tail=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v,i);
            add(v,u,i);
        }
        cnt=num=0;
        memset(dfn,0,sizeof(dfn));
        dfs(1,-1);
        memset(head,-1,sizeof(head));
        m<<=1;
        for(int i=0;i<m;i++)
            if(belong[edge[i].u]!=belong[edge[i].v])
            {
                add(belong[edge[i].u],belong[edge[i].v],0);
                add(belong[edge[i].v],belong[edge[i].u],0);
            }
        memset(dp,0,sizeof(dp));
        dp[1]=1;
        mx=-1;
        dfs(1);
        mx=-1;
        memset(dp,0,sizeof(dp));
        dp[node]=1;
        dfs(node);
        printf("%d\n",num-mx);
    }
}



猜你喜欢

转载自blog.csdn.net/chaoweilanmao/article/details/50790268