tarjan(有向图&点双&边双)

本文介绍了如何使用Tarjan算法求解有向图的强连通分量和无向图的双连通分量。通过分析Luogu平台上的例题,阐述了算法思路,并提供了代码模板。在有向图中,如果一对夫妻的婚姻关系破坏后,仍能形成新的强连通分量,那么该婚姻被认为是不安全的。在无向图中,割点和桥的概念被用来识别双连通分量,对于割点的判断标准以及割边的识别也进行了详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

强连通分量(有向图)

求法:tarjan

例题

https://www.luogu.org/problemnew/show/P2863
https://www.luogu.org/problemnew/show/P2341
在这里插入图片描述
Description
我们已知n对夫妻的婚姻状况,称第i对夫妻的男方为Bi,女方为Gi。若某男Bi与某女Gj曾经交往过(无论是大学,高中,亦或是幼儿园阶段,i≠j),则当某方与其配偶(即Bi与Gi或Bj与Gj)感情出现问题时,他们有私奔的可能性。不妨设Bi和其配偶Gi感情不和,于是Bi和Gj旧情复燃,进而Bj因被戴绿帽而感到不爽,联系上了他的初恋情人Gk……一串串的离婚事件像多米诺骨牌一般接踵而至。若在Bi和Gi离婚的前提下,这2n个人最终依然能够结合成n对情侣,那么我们称婚姻i为不安全的,否则婚姻i就是安全的。给定所需信息,你的任务是判断每对婚姻是否安全。

思路

  1. 经过一番思考,我们发现一个人只有一个婚姻关系,以及一个情侣关系。如果破坏他的婚姻关系,那么一定要在和他的情侣关系在一起,也就是说如果一对夫妻在一个强连通分量里,就会有二种不同的方式互相到达
  2. 所以我们夫妻关系男向女,情侣女向男连边,跑tarjan

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map> 
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
	char ch=' ';int f=1;int x=0;
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
const int N=10100;
const int M=500100;
map<string,int> ma;
struct node
{
	int v,nxt;
}edge[M];
int head[N],cnt;
void add(int u,int v)
{
	cnt++;
	edge[cnt].v=v;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}
int dfn[N],low[N],t=0;
int belong[N],cc=0;
bool ins[N];
int s[N],top=0;
void dfs(int u)
{
	dfn[u]=low[u]=++t;
	ins[u]=true;s[++top]=u;
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].v;
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[v],low[u]);
		}
		else if(ins[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])
	{
		cc++;
		while(s[top]!=u)
		{
			belong[s[top]]=cc;
			ins[s[top]]=false;
			top--;
		}
		belong[u]=cc;
		ins[u]=false;
		top--;
	}
}
int main()
{
	int n=read();
	string str;
	for(int i=1;i<=n;i++)
	{
		cin>>str;ma[str]=i;
		cin>>str;ma[str]=i+n;
		add(i,i+n);
	}
	int m=read();
	for(int i=1;i<=m;i++)
	{
		cin>>str;int u=ma[str];
		cin>>str;int v=ma[str];
		add(v,u);
	}
	for(int i=1;i<=(n<<1);i++)
	{
		if(!dfn[i])
		{
			dfs(i);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(belong[i]==belong[i+n])
		{
			cout<<"Unsafe"<<endl;
		}
		else
		{
			cout<<"Safe"<<endl;
		}
	}
	return 0;
}

双连通分量(无向图)

割点和桥

在这里插入图片描述

模板:求割点

https://www.luogu.org/problemnew/show/P3388

思路

  1. 对于一个点(非root)来说只要有一个儿子的low>=父亲的dfn,它就是割点
  2. 对于root来说,他需要有至少两个这样点儿子
  3. 注意,若tarjan图不连通,需要从每个点tarjan一遍
  4. 注意,若要统计割点数量,需要保证每个割点只被统计一次
const int N=20100;
const int M=200100;
struct node
{
	int nxt,v;
}edge[M];
int head[N],cnt;
void add(int u,int v)
{
	cnt++;
	edge[cnt].v=v;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}

int dfn[N],low[N],t,root,sum;
bool cut[N];

void tarjan(int u)
{
	dfn[u]=low[u]=++t;
	int son=0;
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])
			{
				son++;
				if(u!=root||son>1) 
				{
					if(!cut[u]) sum++;
					cut[u]=true;
				}
				
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
int main()
{
	int n,m;
	n=read();m=read();
	int u,v;
	for(int i=1;i<=m;i++)
	{
		u=read();v=read();
		add(u,v);add(v,u);
	}

	for(int i=1;i<=n;i++)
		if(!dfn[i]) root=i,tarjan(i);
	
	cout<<sum<<endl;
	for(int i=1;i<=n;i++)
	if(cut[i]) cout<<i<<" ";

	return 0;	
}

割边

  1. 注意,cnt从零开始
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值