专题清理之scc-tarjan缩点

题目

loj #10091 luogu P2341 [HAOI2006]受欢迎的牛
loj #10092 luogu P2272 [ZJOI2007]最大半连通子图
luogu P2746 #10093. [USACO5.3]校园网Network of Schools/网络协议
loj #10094. 「一本通 3.5 练习 2」消息的传递
loj #10095 P1262 间谍网络
loj #10096 luogu P3627 [APIO2009]抢掠计划
luogu P2515 [HAOI2010]软件安装

相应题解

受欢迎的牛
最大半连通子图
校园网Network of Schools/网络协议
消息的传递
间谍网络
抢掠计划
软件安装

现在让我们进入正题

1.tarjan的板子

找强联通分量

void tarjan(int u){
    dfn[u]=low[u]=++num;sta[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        if(!dfn[edge[i]._v]){
            tarjan(edge[i]._v);
            low[u]=min(low[u],low[edge[i]._v]);
        }else if(!bel[edge[i]._v])low[u]=min(low[u],dfn[edge[i]._v]);
    }if(low[u]==dfn[u]){
        bel[u]=++col;
        ++siz[col];
        while(top>=0&&sta[top]!=u){
            bel[sta[top]]=col;
            ++siz[col];
            --top;
        }
        --top;
    }
}

重新建图

inline bool cmp(e a,e b){return ((a._u==b._u)?a._v<b._v:a._u<b._u);}
inline void reset(){
    loop(i,1,m){
        line[i]._u=bel[line[i]._u];
        line[i]._v=bel[line[i]._v];
    }sort(line+1,line+1+m,cmp);
}
inline void rebuild(){
    reset(),clean(head,-1),cnt=0;
    loop(i,1,m)
        if(line[i]._u!=line[i]._v&&(line[i]._u!=line[i-1]._u||line[i]._v!=line[i-1]._v))
            addl(line[i]._u,line[i]._v),++in[line[i]._v];
}

2.tarjan算法的可行性讨论

这是我一直想进行的一部分,今天终于有时间了
想要学透tarjan,想必就一定要将上面那段代码琢磨透,后面在进行割点,桥,双联通分量的时候才可以进行活用
首先明确,tarjan算法找scc(强联通分量)的核心就是正确的维护low值
对于这段核心代码,可以进行如下讨论:

for(int i=head[u];i!=-1;i=edge[i].nxt){
    if(!dfn[edge[i]._v]){
        tarjan(edge[i]._v);
        low[u]=min(low[u],low[edge[i]._v]);
    }else if(!bel[edge[i]._v])low[u]=min(low[u],dfn[edge[i]._v]);
}

1.当u是某一个scc的根节点时

  • 若运行
if(!dfn[edge[i]._v]){
    tarjan(edge[i]._v);
    low[u]=min(low[u],low[edge[i]._v]);
}

意味着走到了一条树边或横叉边,由于v的low大于等于u的dfn,那么这个操作一定不会更新当前u的low值

  • 若运行
else if(!bel[edge[i]._v])low[u]=min(low[u],dfn[edge[i]._v]);

意味着走到了一条返祖边,而对于当前讨论的情况来讲,不可能经过返祖边,故不会运行这一句

又有:u可能连接到的下一个节点是:同分量中的节点(树边)或不同分量中的节点(横叉边(返祖边不可能)),当u经过一条横叉边的时候,到达的点v的dfn一定比u小,故也对low无影响

综上所述,当u是某一个scc的根节点时,上述代码可以正确的维护low值
2.当u是某一个scc的非根节点时
此时u可能经过的边为:
1.树边
2.指向已访问过的节点的横叉边
3.指向未访问过的节点的横叉边
4.返祖边

  • 若运行
if(!dfn[edge[i]._v]){
    tarjan(edge[i]._v);
    low[u]=min(low[u],low[edge[i]._v]);
}

此时若经过的是树边,那么仅在edge[i]._v和scc根节点相连时才会更新u的low值
若经过指向未访问过的节点的横叉边,新节点的low值不可能大于u的low值,故low值不会改变

  • 若运行
else if(!bel[edge[i]._v])low[u]=min(low[u],dfn[edge[i]._v]);

此时经过的是返祖边,low[edge[i]._v]不可能大于low[u],故正常更新

对于指向已访问过的节点的横叉边 ,本就不应该对low产生影响,程序中也没有涉及相关操作

即,当u是某一个scc的非根节点时,上述代码可以正确的维护low值

综上,这段程序可以正确的维护出low值,从而达到缩点的目的


上面本人用了一个庞杂的方式来证明了tarjan的可行性,但其实tarjan缩点算法在发明的时候是根据dfs序:在深度优先搜索树中,同属于一个强联通分量的点一定在同一颗子树下,从这点入手,可以更加方便的理解tarjan:
在这里插入图片描述
假设有上图这样的深度优先搜索树,每一个圆圈为一个强联通分量,则对于某一个分量中的点,有且只有4种可能的连边(不可能有向后边的分量连的边,否则与题设矛盾),分情况处理就好

时间复杂度估计:tarjan实质是将所有点遍历了一遍,O(n),重建图将每个边遍历一次,sort,再遍历一次,O(max(m,logm))=O(m),故总复杂度O(n+m)

3.关于scc

scc:强联通分量,实质上就是由一个个环组成的,可能环套环,环连环等

4.命题规律

以下纯属蒟蒻口胡,大佬莫见怪
题目中的缩点一般起一个简化问题的作用:一般题目是给了有向图,而缩点后就变成了严格的DAG(有向无环图),DAG实质上是由多条链组成的,这就达到了简化问题的目的
由于简化后的DAG可以是多条链,也可以是一棵树,还可以是一个森林,故tarjan缩点可以和多种算法融合起来,比如:spfa或拓扑排序(似乎两者在找最长链和统计最长链条数时可以相互替代),树形DP,LCA相关算法,搜索等
(比如说,HAOI2010软件安装)
而一般需要缩点的题要么描述的是抽象但可以转化为图论且有传递性的问题,要么直接是图论问题,具体问题具体分析,注意将问题转化和简化
另:由于缩点一般代码长度不短,所以推荐边输出变量边构造代码,把错误扼杀在萌芽之中

5.关于tarjan的设计

蒟蒻脑胡了一下,发现tarjan的核心是在dfs序上面,而dfs序又同时是RMQ求LCA的核心,也许这就是不变生的万变之冰山一角吧
——2019/6/1


6.tarjan缩点的模式识别

不要问我模式识别是什么,我也不知道

  • 特征:有联通关系可能构成环,构成环后可能对接下来的分析造成影响
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值