tarjan 缩点笔记
强联通分量
对于图中的两个点 u u u 和 v v v,若分别存在一条路径使得 u → v , v → u u\to v,v\to u u→v,v→u,则称 ( 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 u→v,且 v → z v\to z v→z , z ↛ u z\not\to u z→u,则显然 z z z 不与 u u u 在同一个强联通分量中,称 v → z v\to z v→z 为横向边。
- 后向边:若 u → v u\to v u→v,且 v → z v\to z v→z , z → u z\to u z→u,则显然 u , v , z u,v,z u,v,z 在同一个强联通分量中,称 v → z v\to z v→z 为后向边。
显然,横向边不构成一个包含 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 ;
}
参考文献:
例题讲解:
s o l u t i o n solution solution:显然,如果是一个环,那么肯定,把这个环中所有点都遍历一遍是这个环的最优贡献,因此我们按照此贪心利用 tarjan 缩环为点,得到一张 D A G DAG DAG,然后拓扑后跑 d p dp dp 即可。
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
∀z∈G,z→u 且
∀
z
∈
G
,
z
→
v
\forall z\in G ,z\to v
∀z∈G,z→v,因此
u
→
v
u\to v
u→v 且
v
→
u
v\to u
v→u,所以
u
,
v
u,v
u,v 在同一个强联通分量中。
第二,明星奶牛所在的强联通分量是不会有出边的。
证明:考虑明星奶牛所在的强联通分量为
u
u
u,有出边为
u
→
v
u\to v
u→v。由于所有点都可以到达
u
u
u,因此所有点都可以经过
u
u
u 到达
v
v
v,也就是强联通分量
v
v
v 也是由明星奶牛构成的,但这与我们上一条总结的结论矛盾,因此明星奶牛所在的强联通分量是不会有出边的。
有了这两条结论,做法明了:缩点后找出边为零的点,若改点唯一,则答案为该强联通分量的大小,否则不存在明星奶牛。
显然环中的点任意可达,因此这个环买入的最小值为所有点的 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,bi−fmini),其中 j j j 为可以到达 i i i 的点, b i b_i bi 为 i i i 点(实际上是一个强联通分量)的最大出售值。
答案即为 f n 点 所 在 的 强 联 通 分 量 编 号 f_{n点所在的强联通分量编号} fn点所在的强联通分量编号
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战