强联通分量——学习笔记

温馨提示:接下来讨论的情况全部建立在有向图的基础上。


如果两个点i,j满足i能到j且j能到i,则i和j强联通。所有点都强连通的图是强连通图,一个有向图中的所有最大(就是不能再扩大了)的强连通子图都是这个有向图的强连通分量。强联通分量貌似除了环缩点也没别的作用了。求有向图中的强联通分量可以用Tarjan算法,用递归的方法。复杂度为O(n);


Tarjan的核心就是两个数组dfn和low,其中dfn[i]是i的访问时刻,用一个time(官方说法叫时间戳,并不明白是啥么玩意儿),而low[i]表示i能追溯到的最早时刻。
接着再开一个栈stk,表示目前处理的强连通分量中的点。访问一个点i时,先进行初始化dfn[i]=low[i]v=++ti,然后枚举i的儿子节点们,假设目前处理的i的儿子是son,那么分为两种情况
1.sin还没访问过,然后先访问,然后就追溯到low[son],所以这时low[i]=min(low[i],low[son]);
2.son已访问,此时再分两种
(1) son不在栈里,就表示和i没什么关系了.
(2)son还在栈里,这时用子树节点更新节点第一次出现的时间low[i]=min(low[i],dfn[son]);
给个题 BZOJ 1179 抢掠计划
经典的tarjan环缩点假spfa刷最长路。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500005
using namespace std;
int n,e,m,s,tot[2],tem,sum,top,a[maxn],b[maxn],fa[maxn],lnk[2][maxn],son[2][maxn],nxt[2][maxn],low[maxn],dfn[maxn],stk[maxn],dst[maxn],que[maxn];
bool instk[maxn],vs[maxn];
inline void readi(int &x){
    x=0; char ch=getchar();
    while ('0'>ch||ch>'9') ch=getchar();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _add(int t,int x,int y){
    tot[t]++;
    son[t][tot[t]]=y; nxt[t][tot[t]]=lnk[t][x]; lnk[t][x]=tot[t];
}
void _dfs(int x){
    dfn[x]=low[x]=++tem; instk[x]=true; stk[++top]=x;
    for (int j=lnk[0][x];j;j=nxt[0][j]){
        if (!dfn[son[0][j]]) {_dfs(son[0][j]); if (low[x]>low[son[0][j]]) low[x]=low[son[0][j]];}
        else if (instk[son[0][j]]&&low[x]>dfn[son[0][j]]) low[x]=dfn[son[0][j]];
    }
    if (dfn[x]!=low[x]) return;
    int y; sum++;
    do{
        y=stk[top--]; fa[y]=sum; instk[y]=false; b[sum]+=a[y];
    }while (x!=y);
}
void _spfa(){
    memset(vs,0,sizeof(vs));
    memset(dst,192,sizeof(dst));
    int hed=0,til=1; que[1]=fa[s]; vs[fa[s]]=true; dst[fa[s]]=b[fa[s]];
    while (hed!=til){
        hed=(hed+1)%maxn;
        vs[que[hed]]=false;
        for (int j=lnk[1][que[hed]];j;j=nxt[1][j]){
            if (dst[son[1][j]]<dst[que[hed]]+b[son[1][j]]){
                dst[son[1][j]]=dst[que[hed]]+b[son[1][j]];
                if (!vs[son[1][j]]){
                    til=(til+1)%maxn;
                    que[til]=son[1][j];
                    vs[son[1][j]]=true;
                    if (dst[son[1][j]]>dst[que[(hed+1)%maxn]]) swap(que[til],que[(hed+1)%maxn]);
                }
            }
        }
    }
}
int main()
{
    readi(n); readi(e); tot[0]=tot[1]=0;
    memset(lnk,0,sizeof(lnk));
    memset(nxt,0,sizeof(nxt));
    for (int i=1;i<=e;i++){
        int x,y; readi(x); readi(y); _add(0,x,y);
    }
    for (int i=1;i<=n;i++) readi(a[i]);
    tem=sum=top=0;
    memset(b,0,sizeof(b));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(stk,0,sizeof(stk));
    memset(instk,0,sizeof(instk));
    for (int i=1;i<=n;i++) if (!dfn[i]) _dfs(i);
//  for (int i=1;i<=n;i++) printf("%d ",fa[i]); printf("\n");
    for (int i=1;i<=n;i++)
        for (int j=lnk[0][i];j;j=nxt[0][j]) if (fa[i]!=fa[son[0][j]]) _add(1,fa[i],fa[son[0][j]]);
    readi(s); _spfa(); readi(m); int ans=0;
//  for (int i=1;i<=sum;i++) printf("%d ",dst[i]); printf("\n");
    for (int i=1;i<=m;i++){
        int p; readi(p);
        if (ans<dst[fa[p]]) ans=dst[fa[p]];
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值