返回顶部

Tarjan板子

https://csacademy.com/app/graph_editor/

强连通分量(有向边)

常见题

  • 建好有向图
    找强连通分量,同时记录每个强连通分量中节点的个数
    找节点个数最小的强连通分量
点击查看代码
struct Edge
{
	int to,next;
}edge[N];
void add(int u,int v)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++num;
	stk.push(x);
	vis[x]=1;
	for(int i=head[x];i;i=edge[i].next)
	{
		if(!dfn[edge[i].to])//ShuBian
		{
			tarjan(edge[i].to);
			low[x]=min(low[x],low[edge[i].to]);
		}
		else if(vis[edge[i].to])//FanZuBian/HouXiangBian
		{
			low[x]=min(low[x],dfn[edge[i].to]);
		}
	}
	if(low[x]==dfn[x])
	{
		int y=0;
		t++;
		int sum=0;
		do
		{
			y=stk.top();
			stk.pop();
			vis[y]=0;
			belong[y]=t;
			sum++;
		}while(y!=x);
		if(sum!=1)ans=min(ans,sum);
	}
}

缩点

  • 关于链式前向星缩点的一些事项,有些时候不用存from因为缩点时from就等于i
	for(int j=1;j<=n;j++)
	{
		for(int i=head[j];i;i=edge[i].next)
		{
			if(belong[edge[i].to]!=belong[edge[i].from])
			{	
				add(belong[edge[i].from],belong[edge[i].to],sum[belong[edge[i].to]],edge2,head2);
			}
		}		
	}

统计 出度 如度

	for(int j=1;j<=n;j++)
	{
		for(int i=head[j];i;i=edge[i].next)
		{
			if(belong[edge[i].to]!=belong[edge[i].from])
			{
				out[belong[edge[i].from]]++;
				in[belong[edge[i].from]]++;
			}
		}		
	}

综合应用

Tarjan主要用途是将有环图变为一棵树,然后可能会和SPFA DIJ 拓补排序 DP综合起来
注意拓补排序必须是有向无环图

Trick or Treat on the Farm 采集糖果
求割点+缩点+DP

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int low[N],dfn[N],num,head[N],head2[N],cnt,belong[N],ans=0,sum[N];bool vis[N],cut[N];
stack <int> stk;
int t,n,root;
struct Edge
{
	int to,next,from,w;
}edge[N],edge2[N];
void add(int u,int v,int w,Edge *edge,int *head)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	edge[cnt].from=u;
	edge[cnt].w=w;
	head[u]=cnt;
}
int f[N]={};bool flag[N];
void dp(int fa)
{
//	if(!fa)return;
	
	flag[fa]=true;
//	f[fa]=faw;
	if(fa)
	{
		f[fa]+=sum[fa];
	}
	int faw=f[fa];
	for(int i=head2[fa];i;i=edge2[i].next)
	{
		int son=edge2[i].to;
		if(!f[son])dp(son);
		f[fa]=max(f[fa],faw+f[son]);
//		cout<<son<<" "<<f[son]<<" "<<fa<<' '<<f[fa]<<endl;
	}

}
void tarjan(int now)
{
	dfn[now]=low[now]=++num;
	stk.push(now);
	vis[now]=1;
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!dfn[to])
		{
			tarjan(to);
			low[now]=min(low[now],low[to]);	
		}else if(vis[to])
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
	if(dfn[now]==low[now])
	{
		t++;
		int y;
		do
		{
			y=stk.top();
			stk.pop();
			vis[y]=0;
			sum[t]++;
			belong[y]=t;
		}while(now!=y);
	}
}
int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int x,y;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>x;
		add(i,x,0,edge,head);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		{
			root=i;
			tarjan(i);
		}
	}
//	cout<<t<<endl;
//	for(int i=1;i<=n;i++)cout<<belong[i]<<" "<<sum[belong[i]]<<endl;
//	cout<<endl;
	cnt=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=head[i];j;j=edge[j].next)
		{
			if(belong[edge[j].from]!=belong[edge[j].to])
			{
				add(belong[edge[j].from],belong[edge[j].to],sum[belong[edge[j].to]],edge2,head2);
			}
		}
	}
//	for(int i=1;i<=t;i++)f[i]=sum[i];
	int ans[N]={};
	for(int i=1;i<=t;i++)
	{
//		memset(f,0,sizeof(f));
//		f[i]=sum[i];
		if(!flag[i])
		{
			dp(i);	
		}
		
	}
//	cout<<endl;
	for(int i=1;i<=n;i++)
	{
		cout<<f[belong[i]]<<endl;
	}
	return 0;
}
/*
4
1
3
2
3
*/

无向图上Tarjan算法的应用

image
image

求割点

注意是无向图

点击查看代码
void tarjan(int now)
{
	dfn[now]=low[now]=++num;
	int son=0;
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!dfn[to])
		{
			son++;
			tarjan(to);
			low[now]=min(low[now],low[to]);
			
			if(dfn[now]<=low[to])
			{
				
				if((now!=root||son>1)&&!cut[now])cut[now]=true,t++;
			}	
		}else
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
}

求割边

点击查看代码
void tarjan(int now,int in_edge)
{
	dfn[now]=low[now]=++num;
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!dfn[to])
		{
			tarjan(to,i);
			low[now]=min(low[now],low[to]);
			if(dfn[now]<low[to])
			{
				ge++;
				bridge[i]=bridge[i^1]=true;
			}
		}else if(i!=(in_edge^1))
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
}

Tarjan求双连通分量

法1 DFS缩点

点击查看代码
void tarjan(int now,int in_edge)
{
	dfn[now]=low[now]=++num;
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!dfn[to])
		{
			tarjan(to,i);
			low[now]=min(low[now],low[to]);
			if(dfn[now]<low[to])
			{
				ge++;
				bridge[i]=bridge[i^1]=true;
			}
		}else if(i!=(in_edge^1))
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
}
int c[N]={},dcc;
void dfs(int x)
{
	c[x]=dcc;
	for(int i=head[x];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(c[to]||bridge[i])continue;
		dfs(to);
	}
}
for(int i=1;i<=f;i++)
{
	if(!c[i])
	{
		dcc++;
		dfs(i);
	}
}
法2 用栈和vector 缩点正常缩

image

点击查看代码
void tarjan1(int now,int eid)
{
	dfn[now]=low[now]=++num;
	vis[now]=1;
	stk.push(now);
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
//		int id=edge[i].id;
		if(!dfn[to])
		{
			tarjan1(to,i);
			low[now]=min(low[now],low[to]);
		}else if(i!=(eid^1))
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
	if(low[now]==dfn[now])
	{
		cut[eid]=true;
		ge++;
		int x;
		do
		{
			x=stk.top();stk.pop();
			vis[x]=0;
			belong[x]=ge;
		}while(x!=now);
	}
}
### Tarjan点双连通分量
点击查看代码
void tarjan(int now)
{
	low[now]=dfn[now]=++num;
	stk.push(now);
	if(now==root&&!head[now])
	{
		dcc[++ge].push_back(now);//可以再输入时判断from!=to
		return;
	}
	int son=0;
	for(int i=head[now];i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!dfn[to])
		{
			tarjan(to);
			low[now]=min(low[now],low[to]);
			if(dfn[now]<=low[to])
			{
				son++;
				if(now!=root||son>1)cut[now]=true;
				ge++;
				int x;
				do
				{
					x=stk.top();
					stk.pop();
					dcc[ge].push_back(x);
				}while(x!=to);//注意是x!=to,并不是x!=now
				dcc[ge].push_back(now);//割点不一定只在一个人点双中
			}
		}else
		{
			low[now]=min(low[now],dfn[to]);
		}
	}
}

圆方树

与求割点和点双相同

posted @ 2024-03-30 20:04  wlesq  阅读(16)  评论(0编辑  收藏  举报