• tarjan 缩点笔记


    强联通分量

    对于图中的两个点 u u u v v v,若分别存在一条路径使得 u → v , v → u u\to v,v\to u uv,vu,则称 ( u , v ) (u,v) (u,v) 强联通。

    若对于一张图 G G G 中任意两个点都强联通,则称 G G G 为一个强连通图

    一张图往往由多个强联通子图组成(各个强联通图之间可能会有包含关系),对于那些最大的强联通子图(不存在包含关系),我们称其为强联通分量

    不严谨的说,强联通分量就是环。


    缩点

    若一个图是 D A G DAG DAG有向无环图),我们可以根据其拓扑序的无后效性,在图上跑各种 d p , d f s dp,dfs dp,dfs,从而找到解。但如果一个图有环,那么后效性就会使我们非常尴尬(环会导致起初遍历过的点的答案发生改变)。

    但是往往会有一些非常明显的贪心思路,例如一个环的贡献其实就是等于环上所有点的贡献之和。
    因此,我们就可以把一个环 成一个超级点。对图中的所有环进行类似操作,最终图就会转变成一个 D A G DAG DAG,且保留了环的有效贡献,此时我们就可以在图中放心跑各种无后效性的算法。


    Tarjan

    • 横向边:若 u → v u\to v uv,且 v → z v\to z vz z ↛ u z\not\to u zu,则显然 z z z 不与 u u u 在同一个强联通分量中,称 v → z v\to z vz 为横向边。
    • 后向边:若 u → v u\to v uv,且 v → z v\to z vz z → u z\to u zu,则显然 u , v , z u,v,z u,v,z 在同一个强联通分量中,称 v → z v\to z vz 为后向边。

    显然,横向边不构成一个包含 u u u 的环,后向边反之。因此,找强联通分量即为找到最外层的关于 u u u 的后向边。

    我们可以用 d f s dfs dfs 中的栈来实现上述过程,因为 d f s dfs dfs 遍历的是一条由可以到达 u u u 的点和 u u u 可以到达的点组成的路径,我们可以借此来快速判断某一点是否能到达 u u u

    • d f n i dfn_i dfni:时间戳,表示 i i i 被遍历到的时间
    • l o w i low_i lowi:表示在所有能到达 i i i 的点集中, i i i 能到达的 d f n dfn dfn 最小的点的遍历时间。即 i i i 所在的强联通分量中被遍历到最早的点的遍历时间。
    • b e l o n g i belong_i belongi:表示 i i i 所在的强联通分量的编号。
    • v i s i vis_i visi:判断点 i i i 是否在栈中的标记,若在,则 i i i 可到达栈中在它之后的所有点。(考虑 d f s dfs dfs 的原理,遍历一条链)

    code:

    stack<int> p;
    void tarjan(int u)
    {
    	dfn[u]=low[u]=++tt;
    	p.push(u);
    	vis[u]=1;  //在栈中
    	for(int i=head[u];i;i=e[i].nxt)
    	{
    		int v=e[i].v;
    		if(!dfn[v])  //还未遍历过
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]); //尝试用 v 更新 u
    		}
    		else if(vis[v]) low[u]=min(low[v],low[u]);  //v可以到达u,在同一个强联通中,因此选最早被遍历的
    	}
    	if(dfn[u]==low[u])  //u点为所在强联通分量中最早被遍历到的点
    	{
    		scc++;
    		while(1)//栈中,u点之后的所有点都是与u在同一个强联通分量中,与u不同的都已经被处理过了弹出了
    		{
    			int now=p.top();
    			//记录当前强联通分量的贡献
    			belong[now]=scc;
    			vis[now]=0;
    			p.pop();
    			if(now==u) break;
    		}
    	}
    	return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    参考文献:

    初探 tarjan 算法

    TarJan,你真的了解吗

    强联通分量算法解析

    tarjan 详解


    例题讲解:

    1. P3387 【模板】缩点

    s o l u t i o n solution solution:显然,如果是一个环,那么肯定,把这个环中所有点都遍历一遍是这个环的最优贡献,因此我们按照此贪心利用 tarjan 缩环为点,得到一张 D A G DAG DAG,然后拓扑后跑 d p dp dp 即可。

    1. P2341 受欢迎的奶牛

    s o l u t i o n solution solution
    首先,两头明星奶牛 u , v u,v u,v 肯定在同一强联通分量中。
    证明:根据明星奶牛的定义, ∀ z ∈ G , z → u \forall z\in G ,z\to u zG,zu ∀ z ∈ G , z → v \forall z\in G ,z\to v zG,zv,因此 u → v u\to v uv v → u v\to u vu,所以 u , v u,v u,v 在同一个强联通分量中。

    第二,明星奶牛所在的强联通分量是不会有出边的。
    证明:考虑明星奶牛所在的强联通分量为 u u u,有出边为 u → v u\to v uv。由于所有点都可以到达 u u u,因此所有点都可以经过 u u u 到达 v v v,也就是强联通分量 v v v 也是由明星奶牛构成的,但这与我们上一条总结的结论矛盾,因此明星奶牛所在的强联通分量是不会有出边的。

    有了这两条结论,做法明了:缩点后找出边为零的点,若改点唯一,则答案为该强联通分量的大小,否则不存在明星奶牛。

    1. P1073 [NOIP2009 提高组] 最优贸易

    显然环中的点任意可达,因此这个环买入的最小值为所有点的 min ⁡ \min min ,出售的最大值为所有点的 max ⁡ \max max,以此来缩点,更新强联通分量的权值,在 D A G DAG DAG 上拓扑跑 d p dp dp 即可。

    f m i n i fmin_i fmini 为到达 i i i 点时的最小买入价格,则有 f m i n i = min ⁡ ( f m i n j , a i ) fmin_i=\min(fmin_j,a_i) fmini=min(fminj,ai),其中 j j j 为可以到达 i i i 的点, a i a_i ai i i i 点(实际上是一个强联通分量)的最小买入值。

    f i f_i fi 为到达 i i i 点的最大利润,则有 f i = max ⁡ ( f j , b i − f m i n i ) f_i=\max(f_j,b_i-fmin_i) fi=max(fj,bifmini),其中 j j j 为可以到达 i i i 的点, b i b_i bi i i i 点(实际上是一个强联通分量)的最大出售值。

    答案即为 f n 点 所 在 的 强 联 通 分 量 编 号 f_{n点所在的强联通分量编号} fn

  • 相关阅读:
    (续)SSM整合之springmvc笔记(@RequestMapping注解)(P124-130)还没完
    Unity3D 如何用unity引擎然后用c#语言搭建自己的服务器
    网络安全--APT技术、密码学
    【数据结构】单链表定义的介绍及增删查改的实现
    设计模式 代理模式
    免费享受企业级安全:雷池社区版WAF,高效专业的Web安全的方案
    multi-gneration lru系列 - 怎么决定回收anon还是file
    Selenium选择器小结
    跟着官方帮助文档学ICEM网格划分(附视频教程)
    Google Earth Engine —— 利用sentinel-1/2数据集进行土地分类59个参数案例
  • 原文地址:https://blog.csdn.net/SAI_2021/article/details/127946311