如果你和本蒟蒻一样一开始学?不会Tarjan?

一、强连通分量(Strongly Connected Components,SCC)

神级博客,一看就懂

Tarjan的板子(求指正)

void Tarjan(int u)
{
	dfn[u] = ++cnt; //节点u的时间戳+1
	low[u] = cnt; //初始化:回到的最小时间戳节点是自己
	stk[++top] = u;//手写栈,让u入栈
	for(auto v : E[u])
	{
		if(!dfn[v]) 
		{
			Tarjan(v);//没访问过就对下一个节点进行一次dfs
			low[u] = min(low[u],low[v]);
			//暂时对下一个节点的low与自己的low进行toMin操作,让一条链上的节点附属同一个根
		}
		//rt[i] == j表示:节点i属于以j为根节点的强连通分量
		else if(!rt[v]) //下一个节点不是根,即在栈内,即回到一个环的开头
			low[u] = min(low[u],dfn[v]);
			//看看自己的low换成环首的时间戳能不能更小,同时在回溯时影响自己的上一个节点
	}
	if(low[u] == dfn[u])//发现u是环首/祖宗
	{
		rt[u] = u;//计入数组root
		while(stk[top] != u)//栈顶到u的所有元素都同属一个强连通分量
		{
			rt[stk[top]] = u;
			top--;
		}	
		top--;
	}
}

下面是78AI豆包写的注释

// Tarjan算法核心函数:寻找以u为起点的强连通分量(SCC)
// 基于DFS遍历,通过维护时间戳dfn和回溯值low来识别SCC
// 当low[u] == dfn[u]时,栈中从u到栈顶的所有节点构成一个SCC
void Tarjan(int u)
{
    dfn[u] = ++cnt; // 节点u的发现时间戳
    low[u] = cnt;   // 初始化low值为当前时间戳,代表u能回溯到的最早节点是自身
                    // 后续在DFS回溯过程中会被更新为更小的值(若存在环)
    stk[++top] = u; // 压入栈中,表示进入当前DFS路径
    
    for(auto v : E[u]) // 遍历u的所有邻接节点v
    {
        if(!dfn[v]) // v未被访问过
        {
            Tarjan(v); // 递归访问子节点v
            low[u] = min(low[u], low[v]); // 回溯时更新u的low值:
                                          // 若v能通过子树回溯到更早节点,则u也能通过v回溯到该节点
        }
        else if(!rt[v]) // v已被访问且仍在栈中(未被弹出),说明存在反向边u→v
            low[u] = min(low[u], dfn[v]); // 将u的low值更新为v的发现时间戳:
                                          // 表示u可以通过反向边回到更早的节点v
                                          // 注意:此处必须用dfn[v]而非low[v],避免跨SCC的干扰
    
    if(low[u] == dfn[u]) // 若u的low值等于dfn值,说明u无法通过子树或反向边回溯到更早节点
    {                     // 即u是当前SCC的根节点(发现时间最早)
        rt[u] = u;        // 将u标记为自身SCC的根
        while(stk[top] != u) // 从栈顶弹出节点直到u(包括u)
        {
            rt[stk[top]] = u; // 这些节点都属于以u为根的SCC
            top--;
        }	
        top--; // 弹出u自身
    }
}

Kosaraju的板子——更好懂的SCC求解算法

//g2是对于图g反向建边的结果
void dfs1(int u) 
{ 
	vis[u] = true; 
	for (int v : g[u]) 
		if (!vis[v]) dfs1(v); 
	s.push_back(u); 
} 
void dfs2(int u) 
{ 
	color[u] = sccCnt; 
	for (int v : g2[u]) 
		if (!color[v]) dfs2(v); 
} 
void kosaraju() 
{ 
	sccCnt = 0; 
	for (int i = 1; i <= n; ++i)
		if (!vis[i]) dfs1(i); 
	for (int i = n; i >= 1; --i) 
		if (!color[s[i]]) 
			{ ++sccCnt; dfs2(s[i]); } 
}

二、割点与桥

定义

·删掉之后会使得整个图的极大连通分量个数增加的点,叫做割点
·删掉之后会使得整个图的极大连通分量个数增加的边,叫做

性质

1.Tarjan求割点

如果 \exists 节点 p ,其 DFS 子树内 \exists 节点 q ,有:

low[q] \geq dfn[p]

p 是一个割点,因为 q 只能通过 p 达到比 p 的时间戳更小的节点。
例外:如果根节点在 DFS 树上有(至少)两个子节点,那么根节点也是一个割点。

Tarjan求割点的板子

void Tarjan(int u,int father)//doubao说这个有问题,不知道qwq
{
	dfn[u] = ++cnt;
	low[u] = cnt;
	int tot = 0;
	for(auto v : E[u])
	{
		if(!dfn[v])
		{
			Tarjan(v,u);
			low[u] = min(low[u],low[v]);
			tot += (low[v] >= dfn[u]);//如上文性质
		}
		else low[u] = min(low[u],dfn[v]);
	}
	if(u == father) ise[u] = (tot > 1);
	else ise[u] = tot > 0;
}

2.Tarjan求桥

对于边 E(p,q) 来说,如果 p 的子节点是 q ,而 q 的后代通过返祖边只能到达 q ,那么E就是一个桥。类似地,判定方式 :

low[q] > dfn[p]

Tarjan求桥的板子

void Tarjan(int u,int father)
{
	dfn[u] = ++cnt;
	low[u] = cnt;
	for(auto v : E[u])
	{
		if(!dfn[v])
		{
			Tarjan(v,u);
			low[u] = min(low[u],low[v]);
			if(low[v] > dfn[u])
			...	//此时边(u,v)即为一座桥,视题目要求加以标记
		}
		else low[u] = min(low[u],dfn[v]);
	}
}

课后练习补充
· 对于任意一个DAG G ,我们记 a,b 分别是 G 中入度为0的点、出度为0的点的数量,若欲使得 G 变为一个强连通图,至少需要添加有向边数量为:

max(a,b)

2 个赞

你的名字让我想起了一位故人… @tyx
(无恶意)

1 个赞

qwq

+1

orz%%%

qp

唐一潇

:hand_with_fingers_splayed::loudly_crying_face: :hand_with_fingers_splayed:

orz,学会了

不过一般不存root,存的是scc的标号,用来作新图的标号

另外建议不要滥用 \LaTeX 公式,数学公式一般表示数学含义,简单的单词没必要框起来

3 个赞

怎么能叫滥用呢?用 \LaTeX 又没太多缺点

1 个赞

叶老师你怎么来了<(OAO<)你不是说论坛没什么用信友队应该把论坛关掉的吗

1 个赞

叶老师Orz%%%,考前吸吸欧气ww(bs

(帖子已被作者删除)

1 个赞

论坛怎么没用了?我觉得很有用

你找叶老师说

为什么论坛的求助帖在夏令营反而变少了呢?

\LaTeX
\LaTeX
:hot_face:

1 个赞

论坛的求助帖变少了,水帖变多了,甚至系统都坏掉了

怎么是日奈啊!考前吸吸欧气

1 个赞

还是别水了吧
@芙卡洛斯 @我命由我不由天