首页 > 其他分享 >tarjan

tarjan

时间:2024-09-30 18:01:13浏览次数:9  
标签:tarjan int 连通 ++ dfn low now

强连通分量 SSC (缩点)

有向图缩点(把一个强连通分量看成一个点),用于优化。

  • 树枝边:DFS 时经过的边,即 DFS 搜索树上的边

  • 反祖边:也叫回边或后向边,与 DFS 方向相反,从某个结点指
    向其某个祖先的边

  • 横叉边:从某个结点指向搜索树中另一子树中的某结点的边,它
    主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结
    点并不是当前结点的祖先时形成的

  • 前向边:与 DFS 方向一致,从某个结点指向其某个子孙的边,
    它是在搜索的时候遇到子树中的结点的时候形成的

对于每个点维护两个值 \((dfn,low)\) ,\(dfn\) 是 dfs 序,\(low\) 表示这条路走到头能回到祖宗的最小值(或叶子)。

对于一个点 \(u\) ,如果他的 \(low_u\) 等于 \(dfn_u\) ,说明 向下 dfs 时还能回到 \(u\) ,则中间这部分构成一个强连通分量。

如图,先 dfs 到 \(e\) ,dfn[e]==low[e],发现一个强连通分量,\(e\) 出栈,回溯到 \(b\) ,第二个强连通分量,将 \(b,c,d\) 出栈。

缩完点变成有向无环图(DAG),可以跑最短路,可以 dp,很方便。

模板

code
#include<bits/stdc++.h>
using namespace std;
const int N = 10005,M = 100005;
int n,m;
int head[N],tot;
struct E {int u,v;} e[M<<1];
inline void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}

int dfn[N],num,low[N],bl[N],cnt,top,st[N];
vector<int> scc[N];
bool vs[N];

void tj(int u)
{
	dfn[u]=low[u]=++num; vs[u]=1;
	st[++top]=u;
	for(int i=head[u];i;i=e[i].u)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tj(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vs[v])
			low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u])
	{
		++cnt; int now;
		do
		{
			now=st[top--]; vs[now]=0;
			bl[now]=cnt; scc[cnt].push_back(now);
		} while(now!=u);
	}
}
bool ans[N];
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++)  if(!dfn[i]) tj(i);
	printf("%d\n",cnt);
	for(int i=1;i<=n;i++)
	{
		int k=bl[i]; if(ans[k]) continue;
		sort(scc[k].begin(),scc[k].end());
		for(int j:scc[k]) printf("%d ",j); putchar('\n');
		ans[k]=1;
	}
	return 0;
}

割点

无向图求割点,就是删掉后能把图割成几个部分的点,思路和缩点类似,如果 dfn[u]<=low[v] ,说明 \(u\) 以下有一个连通块,且 \(u\) 下面的连通块和 \(u\) 上面的没有联系,所以此时 \(u\) 为割点。

如图 \(B,E,K\) 为割点。

(注意,根节点如果只有一个儿子,他不是割点。)

code
void tj(int s)
{
	dfn[s]=low[s]=++num;
	int son=0;
	for(int i=head[s];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!dfn[to])
		{
			tj(to);
			low[s]=min(low[s],low[to]);
			if(dfn[s]<=low[to])
			{
				son++;
				if(s!=root || son>1)
				{
					if(!ans[s]) cnt++;
					ans[s]=1;
				}
			}
		}
		else	
			low[s]=min(low[s],dfn[to]);
	}
}

割边

无向图求割边,定义和割点差不多。

dfn[u]<low[v] 则 \((u,v)\) 为割边(注意不能 \(=\) ),上图割边有 \(A-B\) 以及 \(E-K\) 和 \(K-L\),因为是无向图,建边时都建成了正向反向两条边,所以要判一下正向反向的两条边,避免原路走回去。

code
void tj(int s,int fa)
{
	dfn[s]=low[s]=++num;
	for(int i=head[s];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa) continue;
		if(!dfn[to])
		{
			tj(to,s);
			low[s]=min(low[s],low[to]);
			if(dfn[s]<low[to]) ans++;
		}
		else low[s]=min(low[s],dfn[to]);
	}
}

点双连通分量

如果一个无向图中删掉任意一个点后图仍连通,则称这个图 “点双连通” ,点双连通分量就是能找到的 “极大” 的点双连通的子图(任意再加一个点都不行)。

割点的会把图割成几个连通块,这些连通块一定满足点双,但不完整,因为与它相邻的割点处于连通块边缘,如果加进连通块也可以(当叶子),所以同一个割点可能同属于好几个点双,不能用 \(color\) 标记,要用数组存一下。

模板

code
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5,M = 2e6+5;
int n,m;
int head[N],tot;
struct E {int u,v;} e[M<<1];
inline void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}

int dfn[N],low[N],num,st[N<<1],top,rt,cnt;
vector<int> vcc[N<<1];
void tj(int u)
{
	dfn[u]=low[u]=++num;
	st[++top]=u;
	int son=0;
	if(u==rt&&!head[u])//特判根
	{
		vcc[++cnt].push_back(u); return;
	}
	for(int i=head[u];i;i=e[i].u)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tj(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])//注意要有儿子才是割点
			{
				son++;
				cnt++; int now;
				do
				{
					now=st[top--];  vcc[cnt].push_back(now);
				} while(now!=v);//注意是不等于儿子
				vcc[cnt].push_back(u);//加入父亲
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	if(n==1)
	{
		printf("1\n1 1\n"); return 0;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		if(x==y) continue;//注意自环,会出问题
		add(x,y); add(y,x);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) rt=i,tj(i);
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	{
		printf("%d ",vcc[i].size());
		for(int j:vcc[i]) printf("%d ",j); putchar('\n');
	}
	return 0;
}

边双连通分量

定义和点双类似,去掉任意一条边仍连通的极大子图,求法和 SSC 缩点有点像,只需要判重边就行了(见求割边)。

边双满足任意两点之间一定存在两条完全没有重复部分的路(如果有重复部分,割掉就断了)。

直接套强连通分量的板子就行,注意必须传边防止重边。

模板

code
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5,M = 2e6+6;
int n,m;
int head[N],tot=1;
struct E {int u,v;} e[M<<1];
inline void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}

int dfn[N],low[N],num,st[N],top,cnt;
vector<int> ecc[N];
void tj(int u,int ed)//防止有重边,必须传边,为了方便对应 tot=1,有点像网络流
{
	dfn[u]=low[u]=++num;
	st[++top]=u;
	for(int i=head[u];i;i=e[i].u)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tj(v,i);
			low[u]=min(low[u],low[v]);
		}
		else if(i!=(ed^1)) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])//跟SSC一模一样
	{
		++cnt; int now;
		do
		{
			now=st[top--]; ecc[cnt].push_back(now);
		} while(now!=u);
	}
}	

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tj(i,-1);
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	{
		printf("%d ",ecc[i].size());
		for(int j:ecc[i]) printf("%d ",j); putchar('\n');
	}
	return 0;
}

注意

板子别打错!!!

update:2024.9.30 更新了代码,微调排版。

标签:tarjan,int,连通,++,dfn,low,now
From: https://www.cnblogs.com/ppllxx-9G/p/18070058

相关文章

  • 强联通分量——Tarjan算法
    Tarjan算法详解参考文章:强连通分量Tarjan算法是一种用于寻找有向图中强联通分量(StronglyConnectedComponents,SCCs)的算法。它是由RobertTarjan在1972年提出的,基于深度优先搜索(DFS)和栈的数据结构。基本概念强联通分量:在一个有向图中,如果一组节点中任意两个节点都可以互相......
  • Tarjan算法缩点
    Tarjan算法缩点一.Tarjan算法缩点详解在图论中,缩点是指将有向图中的强联通分量(SCCs)缩成单个节点,从而得到一个更简单的图结构,称为缩点图或SCC图。Tarjan算法不仅可以用来寻找强联通分量,还可以用来进行缩点操作。基本概念强联通分量:在一个有向图中,如果一组节点中任意两个节点都......
  • Tarjan再学习
    蓝书的那一套理论比较生硬,且不是很深刻,故重学Tarjan。AlexWei《图论Ⅰ》相关定义割点:在无向图中,删去使得连通分量数增加的点被称为割点。割边:在无向图中,删去使得连通分量数增加的边被称为割边。点双连通图:不存在割点的无向图。边双连通图:不存在割边的无向图。点双连通分量:一......
  • Tarjan算法及其应用 总结+详细讲解+详细代码注释
    \(\text{Tarjan}\)1.引入概念1.强连通分量1.定义在有向图\(G\)中,强连通分量就是满足以下条件的\(G\)的子图:从任意一点出发,都有到达另一个点的路径。不存在一个或者几个点,使得这个子图加入这些点后,这个子图还满足上一个性质。为什么要限定”在有向图中“而不是”在任......
  • Tarjan
    P3388【模板】割点(割顶)1、注意在遍历时要储存根节点编号,判断时需要特判根节点#include<bits/stdc++.h>usingnamespacestd;constintN=1e5+10;intn,m,r;intdn,dfn[N],low[N],cnt,buc[N];vector<int>e[N];voiddfs(intid){ //标记时间戳 dfn[id]=low[id]......
  • tarjan里的定义
     强连通分量-OIWiki(oi-wiki.org)   从以u为根的子树中的任意点出发。单次到达(从这个点指向某个点,有一条边)的这些点中的dfn的最小值 以v为根的子树,包含在以u为根的子树中,low[v]所用的子节点,一定也可以被low[u],这个点一定在以u为根的子树里,所以用low[v]  从......
  • 一文轻松搞定 tarjan 算法(二)(附带 tarjan 题单)
    完结篇:tarjan求割点、点双连通分量、割边(桥)(附40道很好的tarjan题目)。上一篇(tarjan求强连通分量,缩点,求边双)tarjan求割点还是求强联通分量的大致思路捏.算法思路:我们把图中的点分为两种:每一个联通子图搜索开始的根节点和其他点。判断是不是割点的方式如下:对于根......
  • tarjan—算法的神(一)cw
    本篇包含tarjan求强连通分量、边双连通分量、割点部分,tarjan求点双连通分量、桥(割边)在下一篇。伟大的RobertTarjan创造了众多被人们所熟知的算法及数据结构,最著名的如:(本文的)连通性相关的tarjan算法,Splay-Tree,Toptree,tarjan求lca等等。注:有向图的强连通分量、无向......
  • tarjan—算法的神(一)
    本篇包含tarjan求强连通分量、边双连通分量、割点部分,tarjan求点双连通分量、桥(割边)在下一篇。伟大的RobertTarjan创造了众多被人们所熟知的算法及数据结构,最著名的如:(本文的)连通性相关的tarjan算法,Splay-Tree,Toptree,tarjan求lca等等。注:有向图的强连通分量、无向......
  • 强连通分量(tarjan)
    前言首先你要知道什么是强连通分量再来,不会的话我给你链接啊!好像上面那个链接已经替代了我:)tarjantarjan这个算法的演示图比较复杂,我推荐看这篇博客,这时你肯定要问了,你都推荐别人的博客了,那我看你干嘛,别急,他没给你代可以给你!我们用\(dfn[x]\)表示点\(x\)dfs序(dfs遍历......