强联通分量:Tarjan&缩点

What is 强联通分量?

让我们举个栗子
在这里插入图片描述
好吧其实不是这个
来我们再举个例子
在这里插入图片描述
强联通分量的定义就是,在有向图一组点中,如果每两个点可以互相到达,那么我们叫这组点是一个强联通分量(Strong Connected Components,简称scc)

比如说我们看上面这个图,第一个中的1,2,3,4就是一个强联通分量,因为他们任意两个点都可以互相到达,但是第二个就不是,因为4号点不能到达其他三个任意的点

算法实现

讲完了定义,我们来看看如何来实现
主要的算法是kosaraju和tarjan,主要用的是tarjan,kosaraju我之前学过,但是因为代码实在有点长,加上好久没写,就忘了…
之前写过一篇洛谷p1726 上白泽慧音 kosaraju的题解
这里还是主要讲tarjan吧
这里介绍几个数组
dfn[x] ——————- x点被访问的时间
low[x] —————— 从x点出发能够到达的最小的dfn值得点的dfn
stack[x] ————----栈,这里建议手写,用STL的可能会爆栈
color[x] ——————x点位于第几个scc,位于同一个scc的点的color值相同
vis[x]——————- 记录x点在不在栈里,如果在是1

我们在外面写一个循环,访问每一个没有访问过的节点(dfn[i]=0),每次我们对当前节点的可以到达的节点进行拓展,同时更新low的值,接下来我们需要判断,如果当前节点的dfn和low的值相等,那么我们在这个scc中的拓展已经结束了,那么我们就把栈里面的所有和当前节点的不同的点弹出,直到当前节点(含)

这个东西,说实话,真的是不太好理解…

如果大家不理解的话可以先把代码背下来,然后去,慢慢理解去,打熟练了之后慢慢就理解了(like me)

下面上代码吧

int n,m,w[N],v[N];
int dfn[N],low[N],dfsxu,stack[N],top,color[N],sum;

inline void tarjan(int u){
    dfn[u]=++dfsxu;
    low[u]=dfsxu;
    vis[u]=true;
    stack[++top]=u;
    for(int i=head1[u];~i;i=e1[i].next){
        int v=e1[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]) low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u]){
        color[u]=++sum;
        vis[u]=false;
        while(stack[top]!=u){
            color[stack[top]]=sum;
            vis[stack[top--]]=false;
        }
        top--;
    }
}

tarjan其实是有很多用的,比如说缩点,就是说,根据题目的条件,我们可以把一个scc缩成一个超级点,这样可以从很大程度上节省我们的时间

比如说这一道题

USACO15JAN Grass Cownoisseur

题目描述

In an effort to better manage the grazing patterns of his cows, Farmer John has installed one-way cow paths all over his farm. The farm consists of N fields, conveniently numbered 1…N, with each one-way cow path connecting a pair of fields. For example, if a path connects from field X to field Y, then cows are allowed to travel from X to Y but not from Y to X.

Bessie the cow, as we all know, enjoys eating grass from as many fields as possible. She always starts in field 1 at the beginning of the day and visits a sequence of fields, returning to field 1 at the end of the day. She tries to maximize the number of distinct fields along her route, since she gets to eat the grass in each one (if she visits a field multiple times, she only eats the grass there once).

As one might imagine, Bessie is not particularly happy about the one-way restriction on FJ’s paths, since this will likely reduce the number of distinct fields she can possibly visit along her daily route. She wonders how much grass she will be able to eat if she breaks the rules and follows up to one path in the wrong direction. Please compute the maximum number of distinct fields she can visit along a route starting and ending at field 1, where she can follow up to one path along the route in the wrong direction. Bessie can only travel backwards at most once in her journey. In particular, she cannot even take the same path backwards twice.

约翰有n块草场,编号1到n,这些草场由若干条单行道相连。奶牛贝西是美味牧草的鉴赏家,她想到达尽可能多的草场去品尝牧草。

贝西总是从1号草场出发,最后回到1号草场。她想经过尽可能多的草场,贝西在通一个草场只吃一次草,所以一个草场可以经过多次。因为草场是单行道连接,这给贝西的品鉴工作带来了很大的不便,贝西想偷偷逆向行走一次,但最多只能有一次逆行。问,贝西最多能吃到多少个草场的牧草。

首先看到这道题,我们应该先缩点,因为在一个强连通分量中的点可以互相到达,逆行就没有意义了,所以我们缩点之后,我们用dis1[i]表示从1到i的最长路(边权是道路出发点的点权),然后我们建一张反图,用dis2[i]表示从i到1的最长路
上述过程可以通过SPFA实现
那么我们的答案就是
a n s = m a x ( a n s , d i s 1 [ u ] + d i s 2 [ v ] − s i z e [ c o l o r [ 1 ] ] ) ( e ( u , v ) ∈ m a p 3 ( 反 图 ) ) ans=max(ans,dis1[u]+dis2[v]-size[color[1]]) (e(u,v)\in map3(反图)) ans=max(ans,dis1[u]+dis2[v]size[color[1]])(e(u,v)map3())
代码:

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cmath>
# include <climits>
# include <iostream>
# include <string>
# include <queue>
# include <vector>
# include <set>
# include <map>
# include <cstdlib>
//# include <stack>
# include <ctime>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define mct(a,b) memset(a,b,sizeof(a))
# define gc getchar()
typedef long long ll;
const int N=1e5+5;
const int inf=0x7fffffff;
const double eps=1e-7;
inline int read(){
    int s=0,w=1;
    char c=gc;
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=gc;}
    while(c>='0'&&c<='9')s=s*10+c-'0',c=gc;
    return s*w;
}
inline void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,m;
int head1[N],head2[N],head3[N],cnt1,cnt2,cnt3;
int dfn[N],low[N],stack[N],dfsxu,top,sum,color[N];
int size[N];
int dis1[N],dis2[N],ans;
bool vis[N];

struct Edge{
    int to,next;
}e1[N],e2[N],e3[N];

inline void add1(int x,int y){
    e1[++cnt1]=(Edge){y,head1[x]},head1[x]=cnt1;
}

inline void add2(int x,int y){
    e2[++cnt2]=(Edge){y,head2[x]},head2[x]=cnt2;
}

inline void add3(int x,int y){
    e3[++cnt3]=(Edge){y,head3[x]},head3[x]=cnt3;
}

inline void tarjan(int u){
    dfn[u]=++dfsxu;
    low[u]=dfsxu;
    vis[u]=true;
    stack[++top]=u;
    for(int i=head1[u];~i;i=e1[i].next){
        int v=e1[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]) low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u]){
        color[u]=++sum;
        vis[u]=false;
        while(stack[top]!=u){
            color[stack[top]]=sum;
            vis[stack[top--]]=false;
        }
        top--;
    }
}

inline void spfa1(int src){
    dis1[src]=size[src];
    queue<int> q;
    vis[src]=true;
    q.push(src);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head2[u];~i;i=e2[i].next){
            int v=e2[i].to;
            if(dis1[v]<dis1[u]+size[v]){
                dis1[v]=dis1[u]+size[v];
                if(!vis[v]) q.push(v),vis[v]=true;
            }
        }
        vis[u]=false;
    }
}

inline void spfa2(int src){
    dis2[src]=size[src];
    queue<int> q;
    vis[src]=true;
    q.push(src);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head3[u];~i;i=e3[i].next){
         	int v=e3[i].to; 
            if(dis2[v]<dis2[u]+size[v]){
                dis2[v]=dis2[u]+size[v];
               
                if(!vis[v]) q.push(v),vis[v]=true;
            }
        }
        vis[u]=false;
    }
}

int main(){
    mct(head1,-1);
    mct(head2,-1);
    mct(head3,-1);
    n=read(),m=read();
    Rep(i,1,m){
        int u=read(),v=read();
        add1(u,v);
    }
    mct(vis,0);
    Rep(i,1,n) if(!dfn[i]) tarjan(i);
    Rep(i,1,n) size[color[i]]++;
    Rep(u,1,n)
        for(int i=head1[u];~i;i=e1[i].next){
            int v=e1[i].to;
            if(color[u]!=color[v]){
            	add2(color[u],color[v]),add3(color[v],color[u]);
            } 
        }
    mct(vis,0);
    spfa1(color[1]);
    mct(vis,0);
    spfa2(color[1]);
    ans=size[color[1]];
    Rep(u,1,n)
        for(int i=head3[color[u]];~i;i=e3[i].next)
            if(dis1[color[u]]>0&&dis2[e3[i].to]>0) ans=max(ans,dis1[color[u]]+dis2[e3[i].to]-size[color[1]]);
    write(ans); 
    puts("");
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值