浅谈 tarjan

割点

就是记录两个数组:dfn[]low[]

其中dfn[]表示访问的顺序,low[u]用来存储 \(u\) 不经过其父亲能到达的最小时间戳。。。

搬一下 wiki 的图。。。

img

我们发现 \(low[v]\ge dfn[u]\) 可以表示不能回到祖先,则 \(u\) 点位割点。。。

直接上代码P3388------>

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5; 
int r;
int n,m,book[N];
int dfn[N],low[N];
vector <int> k[N];
int id=0,cnt;
void dfs(int x,int fa)
{
	id++;
	dfn[x]=id;
	low[x]=id;
	int son=0;
	for(int i=0;i<k[x].size();i++)
	{
		int y=k[x][i];
		if(!dfn[y])
		{
			son++;
			dfs(y,x);
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x]&&x!=r)
			{
				cnt+=!book[x];
				book[x]=1;
			}
		}
		else
		{
			if(1)
			{
				low[x]=min(low[x],dfn[y]);
			}
		}
	}
	if(x==r&&son>1)
	{
		cnt+=!book[x];
		book[x]=1;
	}
	return;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		k[x].push_back(y);
		k[y].push_back(x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		{
			r=i;
			dfs(i,0);
		}
	}
	cout<<cnt<<"\n";
	for(int i=1;i<=n;i++)
	{
	    if(book[i]==1)cout<<i<<" ";
	}
	return 0;
}

强连通分量

如果一个有向图的节点两两互相可达,则称这个图是 强连通的

如果有一张有向图的边换成无向边可得一个连通图,则称这个图是 弱连通的

点双连通分量

步骤:

  1. 首先,找到到割点
  2. 在 dfs 中,使用栈来得到子树的每个点
  3. 你会发现割点一但连接了其他子树和以自己为根的子树,那就不是点双连通分量。所以,只能选择自己的父亲和以自己为根的子树。
  4. 特别的,要特判单独点

见代码P8435

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=4e6+5;
int n,m;
struct edge
{
	int v,nxt;
}e[M];
int head[N],et=0;
inline void add(int u,int v)
{
	et++;
	e[et].v=v,e[et].nxt=head[u];
	head[u]=et;
}
int st[N],top=0,idx=0,dfn[N],low[N];
int cnt=0;
vector<int>k[M];
inline void dfs(int u,int fa)
{
	dfn[u]=low[u]=++idx;
	st[++top]=u;//栈
	int son=0;
	for(int i=head[u];i!=-1;i=e[i].nxt)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			son++;
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])//发现割点
			{
				cnt++;
				while(st[top+1]!=v)
				{
					k[cnt].push_back(st[top--]);
				}
				k[cnt].push_back(u);
			}	
		}
		else if(v!=fa)
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(fa==0&&son==0)k[++cnt].push_back(u);
	return;
}

int main()
{
	memset(e,-1,sizeof e);
	memset(head,-1,sizeof head);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		{
			top=0;
			dfs(i,0);
		}
	}
	cout<<cnt<<'\n';
	for(int i=1;i<=cnt;i++)
	{
		cout<<k[i].size()<<' ';
		for(int j=0;j<k[i].size();j++)
		{
			cout<<k[i][j]<<' ';
		}
		cout<<'\n';
	}
	return 0;
}

边双连通分量

你会发现,把所有割边删掉,剩下的就是所有边双连通分量。

这样就做完了。。。

代码P8436

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=4e6+5;
int n,m;
struct edge
{
	int v,nxt;
}e[M];
int head[N],et=1;
inline void add(int u,int v)
{
	et++;
	e[et].v=v,e[et].nxt=head[u];
	head[u]=et;
}
int idx=0;
int cnt=0,num[N];
int dfn[N],low[N];
bool vis[M];
inline void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++idx;
	for(int i=head[u];i!=-1;i=e[i].nxt)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
			{
				vis[i]=1,vis[i^1]=1;
			}
		}
		else if(fa!=v)
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
}
vector<vector<int>>k;
int book[N];
inline void dfs(int u,int num)
{
	book[u]=num;
	k[cnt-1].push_back(u);
	for(int i=head[u];i!=-1;i=e[i].nxt){
		int v=e[i].v;
		if(vis[i]||book[v])continue;
		dfs(v,u);
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	memset(head,-1,sizeof head);
	memset(e,-1,sizeof e);
	et=-1;
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		if(u==v)continue;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
	for(int i=1;i<=n;i++)
	{
		if(!book[i])
		{
			k.push_back(vector <int> ());
			dfs(i,++cnt);
		}
	}
	cout<<cnt<<'\n';
	for(int i=0;i<cnt;i++)
	{
		cout<<k[i].size()<<' ';
		for(int j=0;j<k[i].size();j++)
		{
			cout<<k[i][j]<<' ';
		}
		cout<<'\n';
	}
	return 0;
}

缩点

你会发现,当dfn[u]==low[u]时,不从自己父亲向上走,你就不能得到更小的low值,这也就说明 \(u\) 是环上一点。。。

具体的,用一个栈来存储搜索顺序,在发现dfn[u]==low时,将 \(u\) 的子树还在栈内的点缩成新的一个点(其实就是打标记)

代码P3387(这题算是一点小应用)

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m;
int p[N];
vector<int>k[N];
vector<int>e[N];
int dfn[N],low[N];
int idx=0,vis[N],st[N],top=0,id[N],sd[N],num=0;
inline void tarjan(int u,int fa)
{
	low[u]=dfn[u]=++idx;
	st[++top]=u;
	vis[u]=1;
	for(int i=0;i<k[u].size();i++)
	{
		int v=k[u][i];
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])//缩点部分
	{
		id[u]=u;
		int v;
		while(v=st[top--])
		{
			id[v]=u;
			vis[v]=0;
			if(u==v)break;
			p[u]+=p[v];
		}
	}
}
int in[N],dis[N];
inline int topsort()
{
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(!in[i]&&id[i]==i)
		{
			q.push(i);
			dis[i]=p[i];
		}
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i];
			if(dis[v]<dis[u]+p[v])
			{
				dis[v]=dis[u]+p[v];
			}
			in[v]--;
			if(in[v]==0)q.push(v);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,dis[i]);
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>p[i];
	}
	for(int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		if(u==v)continue;
		k[u].push_back(v);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])tarjan(i,0);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<k[i].size();j++)
		{
			int u=i,v=k[i][j];
			if(id[u]!=id[v])
			{
				e[id[u]].push_back(id[v]);
				in[id[v]]++;
			}
		}
	}
	cout<<topsort();
	return 0;
}
posted @ 2024-10-18 21:09  tyccyt  阅读(5)  评论(0编辑  收藏  举报