Tarjan 之 强连通分量/割点/割边/双连通分量 (板子总结)

思路来源

https://www.cnblogs.com/nullzx/p/7968110.html(比较清晰的tarjan图示)

https://blog.csdn.net/STILLxjy/article/details/70176689(比较清晰的C++板子)

https://www.cnblogs.com/letlifestop/p/10262951.html(割点代码参考)

https://blog.csdn.net/lzc504603913/article/details/79993428(双连通分量)

心得

tarjan属于dfs相关,蓝桥杯考的还是很多的

现在补一下板子,争取以后都能手敲出来tarjan

还有边的那个别用初始化head -1和~i了

以后用e[++cnt]和head初始为0的版本,比较好用

也不用把每个的下标可以减1了

开的数组虽多,但命名还算挺清楚的吧

计数器只会命tot num cnt的我枯了 这么多计数器

回头再总结一下割点、割边、双连通分量和边双联通分量的板子

其实就是魔改tarjan嘛,就是怕自己忘了

强连通分量板子(以poj2186为例)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e4+5;
const int maxm=5e4+5;
int n,m,u,v;
int res,ans;//最后答案 出度为0点的个数 
int head[maxn],cnt;
int low[maxn],dfn[maxn],num;//最早非负祖先时间戳 时间戳 
int stack[maxn],top,now;//用数组模拟栈 栈顶 当前栈顶值 
int par[maxn],tot;//染色 颜色数 
bool in[maxn];//是否在栈中 
int out[maxn];//如果不在一个连通分量里 统计出度  
int sum[maxn];//每个连通分量里的点的个数 
struct edge
{
	int to,next;
}e[maxm];
void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int u)
{
	low[u]=dfn[u]=++num;
	in[u]=1;
	stack[++top]=u;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(in[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])//环的第一个点 
	{
		tot++;
		do
		{
			now=stack[top--];
			par[now]=tot;
			sum[tot]++; 
			in[now]=0; 
		}while(now!=u);
	}
}
void init()
{
	memset(head,0,sizeof head);
	memset(low,0,sizeof low);
	memset(dfn,0,sizeof dfn);
	memset(par,0,sizeof par);
	memset(sum,0,sizeof sum);
	memset(in,0,sizeof in);
	memset(out,0,sizeof out); 
	res=ans=cnt=num=top=tot=0;
}
void solve()
{
	for(int i=1;i<=n;++i)
	if(!dfn[i])dfs(i);
	for(int u=1;u<=n;++u)
	{
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].to;//u->v的有向边 
			//不在同一个连通分量里
			if(par[u]!=par[v])out[par[u]]++; 
		}
	} 
	for(int i=1;i<=tot;++i)//枚举连通分量 
	{
		if(!out[i])//该连通分量 出度为0
		{
			ans++;
			res=sum[i]; 
		} 
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		init();
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		solve();
		printf("%d\n",ans==1?res:0);
	}
	return 0;
}

割点板子(以poj1144为例)

注意特判根节点,>=2棵从根节点起dfs的子树才是割点

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=110;
const int maxm=2e4+10;
int head[maxn],cnt;
int low[maxn],dfn[maxn],num;
int n,ans;
int u,v;
bool cut[maxn];//是否是割点 
struct edge
{
	int to,next;
}e[maxm];
void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void init()
{
	memset(cut,0,sizeof cut);
	memset(head,0,sizeof head);
	memset(low,0,sizeof low);
	memset(dfn,0,sizeof dfn);
	ans=num=cnt=0;
}
void dfs(int u,int fa)
{
	low[u]=dfn[u]=++num;
	int ch=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(!dfn[v])
		{
			dfs(v,u);
			ch++;//从u这里向下dfs的子树的数量 
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])cut[u]=1;//有一个 u就是割点 
		}
		else if(v!=fa)
		low[u]=min(low[u],dfn[v]);
	}
	if(fa==0&&ch==1)cut[u]=0;//特判根节点 有两棵及以上子树才可 
}
int main()
{
	while(~scanf("%d",&n)&&n)
	{
		init();
		while(~scanf("%d",&u)&&u)
		{
			while((getchar())!='\n')
			{
				scanf("%d",&v);
				add(u,v);add(v,u);
			}
		}
		for(int i=1;i<=n;++i)
		if(!dfn[i])dfs(i,0);
		for(int i=1;i<=n;++i)
		if(cut[i])ans++;
		printf("%d\n",ans);
	}
	return 0;
}

割边板子(以牛客小白月赛12 I-华华和月月逛公园 为例)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e5+10;
const int maxm=6e5+10;
int n,m,head[maxn];
int cnt=1;//这里是为了初始情况下,2和3的异或对应关系 
int dfn[maxn],low[maxn],num;
bool bridge[maxm];
int ans,u,v;
struct edge
{
    int to,next;
}e[maxm];
void add(int u,int v)
{
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int in)
{
    low[u]=dfn[u]=++num;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            dfs(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
            bridge[i]=bridge[i^1]=1;
        }
        else if(i!=(in^1))
        low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;++i)
    if(!dfn[i])dfs(i,0);
    for(int i=2;i<cnt;i+=2)
    if(bridge[i])ans++;//bridge 
    printf("%d\n",m-ans);
    return 0;
}

双连通分量板子

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/89037006
今日推荐