Tarjan 学习笔记

相关链接

相关概念:https://oi-wiki.org/graph/concept/

题单:https://www.luogu.com.cn/training/576466#problems

强连通分量

Tarjan 可以用于求强连通分量,然后就可以缩点了。

具体的,我们建出一个 dfs 树,设 \(dfn_u\)\(u\) 被访问的编号,\(low_u\) 表示 \(u\) 能连到最靠祖先的 \(dfn\)
那么在从 \(u\) 搜索 \(v\) 时,我们将 \(u\) 先入栈,可以知道:

  • 如果一个点未被访问,那么 \(low_u = low_v\),因为 \(v\) 可以到的地方,\(u\) 一定可以到。
  • 如果一个点已被访问,且在栈中,那么 \(low_u=dfn_v\),原因是这时 \(v\)\(u\) 的祖先。
  • 其他情况不用管。

注意到一个强连通分量有且仅有一个点使得 \(low_u=dfn_u\),那就是该分量的起始节点,那么这个强连通分量的所有的点在 Tarjan 算法的栈中在 \(u\) 之后。

相关题目:B3609 [图论与代数结构 701] 强连通分量

代码
void dfs(int u) {
	vis[u]=1;
	low[u]=dfn[u]=++dfncnt;
	stk[++stktop]=u;
	for(auto v:G[u]) {
		if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]) {
		anscnt++;
		while(1) {
			int v=stk[stktop--];
			vis[v]=0;
			ans[anscnt].push_back(v);
			if(v==u) break;
		}
		sort(ans[anscnt].begin(),ans[anscnt].end());
	}
}

缩点

我们学会了如何去搜强连通分量,那么把这些强联通分量缩成一个点,就是缩点。
一张有向图,缩点之后会变成一个 DAG。

相关题目:【模板】缩点

代码
int n,m;
int val[maxn],totval[maxn];
vector<int> G[maxn];
int low[maxn],dfn[maxn],dfncnt;
int stk[maxn],top,anscnt,bel[maxn];
bool vis[maxn];
int a[maxn],b[maxn];
int deg[maxn],dep[maxn],dp[maxn];
void dfs(int u) {
	vis[u]=1;
	dfn[u]=low[u]=++dfncnt;
	stk[++top]=u;
	for(int v:G[u]) {
		if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]) {
		anscnt++;
		while(1) {
			int v=stk[top--];
			vis[v]=0;
			bel[v]=anscnt;
			if(u==v) break;
		}
	}
}
signed main() {
	in2(n,m);
	For(i,1,n) in1(val[i]);
	For(i,1,m) {
		in2(a[i],b[i]);
		G[a[i]].push_back(b[i]);
	}
	For(i,1,n) if(!dfn[i]) dfs(i);
	For(i,1,n) G[i].clear(),totval[bel[i]]+=val[i];
	For(i,1,m) a[i]=bel[a[i]],b[i]=bel[b[i]];
	For(i,1,m) if(a[i]!=b[i]) G[a[i]].push_back(b[i]),deg[b[i]]++;
	queue<int> q;
	For(i,1,n) if(!deg[i]) q.push(i),dp[i]=totval[i];
	int ans=0;
	while(!q.empty()) {
		int u=q.front(); q.pop();
		for(int v:G[u]) {
			dep[v]=dep[u]+1;
			dp[v]=max(dp[v],dp[u]+totval[v]);
			deg[v]--;
			if(!deg[v]) q.push(v);
		}
		ans=max(ans,dp[u]);
	}
	cout<<ans<<'\n';
}

边双连通分量

缩完点之后每一个点都是一个边双。
注意一下在无向图中有重边和自环时不可以用 dfs(int u,int fa) 记录父节点,而是 dfs(int u,int edge) 记录上一条过来的边。

代码
int n,m;
int low[maxn],dfn[maxn],dfncnt;
bool vis[maxn];
int edgecnt=1;
vector<int>G[maxn],ans[maxn],idx[maxn];
int stk[maxn],top;
int anscnt=0;
void dfs(int u,int fa) {
//	cout<<u<<' '<<fa<<'\n';
	vis[u]=1;
	dfn[u]=low[u]=++dfncnt;
	stk[++top]=u;
	For(i,0,G[u].size()-1) {
		int v=G[u][i],e=idx[u][i];
		if(e==(fa^1)) continue;
		if(!dfn[v]) dfs(v,e),low[u]=min(low[u],low[v]);
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) {
		++anscnt;
		while(1) {
			int v=stk[top--];
			vis[v]=0;
			ans[anscnt].push_back(v);
			if(u==v) break;
		}
	}
}
signed main() {
	in2(n,m);
	For(i,1,m) {
		int x,y;
		in2(x,y);
		G[x].push_back(y);
		G[y].push_back(x);
		idx[x].push_back(++edgecnt);
		idx[y].push_back(++edgecnt);
	}
	For(i,1,n) if(!dfn[i]) dfs(i,0);
	cout<<anscnt<<'\n';
	For(i,1,anscnt) {
		cout<<ans[i].size()<<' ';
		for(auto v:ans[i]) cout<<v<<' ';
		puts("");
	}
}

割点

当前节点 \(u\) 有一个孩子 \(v\) 使得 \(low_v\le dfn_u\)

割边

当前节点 \(u\) 有一个孩子 \(v\) 使得 \(low_v< dfn_u\)

posted @ 2024-10-16 09:04  coding_goat_qwq  阅读(6)  评论(0编辑  收藏  举报