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/网络协议 消息的传递 间谍网络 抢掠计划 软件安装
现在让我们进入正题
找强联通分量
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]; }这是我一直想进行的一部分,今天终于有时间了 想要学透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)
scc:强联通分量,实质上就是由一个个环组成的,可能环套环,环连环等
以下纯属蒟蒻口胡,大佬莫见怪 题目中的缩点一般起一个简化问题的作用:一般题目是给了有向图,而缩点后就变成了严格的DAG(有向无环图),DAG实质上是由多条链组成的,这就达到了简化问题的目的 由于简化后的DAG可以是多条链,也可以是一棵树,还可以是一个森林,故tarjan缩点可以和多种算法融合起来,比如:spfa或拓扑排序(似乎两者在找最长链和统计最长链条数时可以相互替代),树形DP,LCA相关算法,搜索等 (比如说,HAOI2010软件安装) 而一般需要缩点的题要么描述的是抽象但可以转化为图论且有传递性的问题,要么直接是图论问题,具体问题具体分析,注意将问题转化和简化 另:由于缩点一般代码长度不短,所以推荐边输出变量边构造代码,把错误扼杀在萌芽之中
蒟蒻脑胡了一下,发现tarjan的核心是在dfs序上面,而dfs序又同时是RMQ求LCA的核心,也许这就是不变生的万变之冰山一角吧 ——2019/6/1
不要问我模式识别是什么,我也不知道
特征:有联通关系且可能构成环,构成环后可能对接下来的分析造成影响