割点&&桥&&边双&&点双

定义:

割点:将原图中的某一点以及它所连的边删除后,原图不连通

:将原图中的某一边删除后,原图不连通。

边双连通分量:原图中意删除一边后还连通的极大连通子图。

点双连通分量:原图中任意删除一点后还连通的极大连通子图。

求法:

割点:

考虑原图的 dfs 生成树,对于树边更新 : \(low[u]=\min(low[u],low[v])\),对于非树边 \(low[u]=\min(low[u],dfn[v])\)

关键点:假如对于 \(u\) 的子节点 \(v\)\(low[v] \ge dfn[u]\),那么 \(u\) 为割点

为什么呢?原因就在于 \(low\) 的定义是能够到达的节点最早的时间戳,那么假如 \(low[u] \ge dfn[v]\) ,就代表 \(u\) 实际上并不能通过别的节点来到 \(u\) 以上的节点,就代表假如割掉了 \(u\)\(v\) 就不能到达 \(u\) 以上的节点了,所以割掉了 \(u\)\(v\) 就寄了。

代码:

void tarjan(int u,int fa){
	dfn[u]=low[u]=++tim;
	int child=0;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(!dfn[v]){
			child++;
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(fa!=-1&&low[v]>=dfn[u]){
				cut[u]=true;
			}
		}
		else if(dfn[v]<dfn[u]&&v!=fa){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(fa==-1&&child>=2){
		cut[u]=true;//这里注意 u 为根节点的情况
	}
	
	return;
}

桥:

还是一样,使用 tarjan 求解。

这里有一个性质:\(low[v]>dfn[u]\)\(u\)\(v\) 连的边就是一个桥了。

证明同上。

代码:

#include<bits/stdc++.h>
#define L long long  
using namespace std;
const int mod=1000000007,N=1145;
L pre[N],dfn[N],low[N],n,m,dt,ans;
bool vis[N];
vector<L> G[N];
struct edge{
	L from,to;
}a[N];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void dfs(L fa ,L now){
	dfn[now]=low[now]=++dt;
	L len=G[now].size();
	for(L i=0;i<len;i++){
		L next=G[now][i];
		if(next!=fa&&dfn[next]) low[now]=min(low[now],dfn[next]);
		if(!dfn[next]){
			vis[now]=false;
			dfs(now,next);
			if(dfn[now]<low[next]){
				a[++ans].from=min(now,next);
				a[ans].to=max(now,next);
			}
			low[now]=min(low[now],low[next]);
		}
	}
}
bool cmp(edge p,edge q){
	if(p.from!=q.from) return p.from<q.from;
	return p.to<q.to;
}
int main(){
	n=read(),m=read();
	memset(vis,true,sizeof(vis));
	for(L i=1;i<=m;i++){
		L u=read(),v=read();
		G[v].push_back(u);
		G[u].push_back(v);
	}
	for(L i=1;i<=n;i++) if(!dfn[i]) dfs(i,i);
	sort(a+1,a+ans+1,cmp);
	for(L i=1;i<=ans;i++){
		printf("%lld %lld\n",a[i].from,a[i].to);
	}
	return 0;
}

边双:

与强连通分量一样,当遍历完 \(u\) 的所有子节点后,若 \(low[u]=dfn[u]\),上面的都是边双的元素。

代码如下:


#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e5+10;
struct edge{
	int v,next;
}edges[N*10];
int head[N],idx=2;
int dfn[N],low[N],clk;
int st[N],top;
bool Cut[N*10];
vector<int>ans[N];
int edcc;

void add_edge(int u,int v){
	idx++;
	edges[idx].v=v;
	edges[idx].next=head[u];
	head[u]=idx;
	return;
}

void tarjan(int u,int fa){
	dfn[u]=low[u]=++clk;
	st[++top]=u;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa)continue;
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				Cut[i]=Cut[i^1]=true;
			}
		}
		else{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		edcc++;
		do{
			ans[edcc].push_back(st[top]);
		}while(st[top--]!=u);
	}
	
	return;
}

signed main(){
	std::ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		add_edge(x,y);
		add_edge(y,x);
	}
	tarjan(1,-1);
	cout<<edcc<<endl;
	
	return 0;
}

点双:

点双即是找到割点后,将割点上的所有元素出栈,并加入这个点双中(包括割点也要加入),但不要将割点出栈(因为有可能割点属于多个点双)。

代码:


#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+10;
struct edge{
	int v,next;
}edges[N];
int head[N],idx;
int n,m;

int low[N],dfn[N],clk;
vector<int>ans[N];
int st[N],top;
bool Cut[N];
int vdcc,root;

void add_edge(int u,int v){
	idx++;
	edges[idx].v=v;
	edges[idx].next=head[u];
	head[u]=idx;
	return;
} 

void tarjan(int u){
	dfn[u]=low[u]=++clk;
	st[++top]=u;
	if(u==root&&(!head[u])){
		vdcc++;
		ans[vdcc].push_back(u);
		return;
	}
	int child=0;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(!dfn[v]){
			tarjan(v);
			child++;
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]){
				if(u!=root||child>1)Cut[u]=true;
				vdcc++;
           int tmp;
				do{
             	tmp=st[top--];
					ans[vdcc].push_back(tmp);
				}while(tmp!=v);
				ans[vdcc].push_back(u);
			}
		} 
		else low[u]=min(low[u],dfn[v]);
	}
	return;
}

signed main(){
	std::ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		if(x==y)continue;
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)if(!dfn[i]){
		root=i;
		tarjan(i);
	}
	cout<<vdcc<<endl;
	
	
	return 0;
}

posted @ 2024-04-25 13:05  Little_corn  阅读(13)  评论(0编辑  收藏  举报