1. 程式人生 > 其它 >雙連通分量

雙連通分量

前置知識

Tarjan入門

定義

對於無向圖中的兩點 \(u,v\),若無論刪去哪條邊都不能使得它們不連通,那麼我們稱 \(u,v\) 邊雙連通

對於無向圖中的兩點 \(u,v\),若無論刪去哪個點都不能使得它們不連通,那麼我們稱 \(u,v\) 點雙連通

其中邊雙連通具有傳遞性,點雙連通不具有傳遞性。

邊雙連通

去掉橋,分成的個連通塊就是一個個邊雙連通分量。

直接Tarjan求割點,然後刪掉橋就可以得到邊雙連通分量。

不過一般來說我們不直接刪掉橋,而是以每個點開始走dfs,不走割邊,然後直接染色即可。

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout);
#define Better_IO 1
namespace IO{
	inline bool blank(const char &c){
		return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
	}
	#if Better_IO==true
		char buf[(1<<20)+3],*p1(buf),*p2(buf);
		char buf2[(1<<20)+3],*p3(buf2);
		const int lim=1<<20;
		inline char gc(){
			if(p1==p2) p2=(p1=buf)+fread(buf,1,lim,stdin);
			return p1==p2?EOF:*p1++;
		}
		#define pc putchar
	#else
		#define gc getchar
		#define pc putchar
	#endif
	inline void gs(char *s){
		char ch=gc();
		while(blank(ch) ) {ch=gc();}
		while(!blank(ch) ) {*s++=ch;ch=gc();}
		*s=0;
	}
	inline void gs(std::string &s){
		char ch=gc();s+='#';
		while(blank(ch) ) {ch=gc();}
		while(!blank(ch) ) {s+=ch;ch=gc();}
	}
	inline void ps(char *s){
		while(*s!=0) pc(*s++);
	}
	inline void ps(const std::string &s){
		for(auto it:s) 
			if(it!='#') pc(it);
	}
	template<class T>
	inline void read(T &s){
		s=0;char ch=gc();bool f=0;
		while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
		while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
		if(ch=='.'){
			db p=0.1;ch=gc();
			while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
		}
		s=f?-s:s;
	}
	template<class T,class ...A>
	inline void read(T &s,A &...a){
		read(s);read(a...);
	}
};
using IO::read;
using IO::gs;
using IO::ps;
const int M=6e5+3;
const int N=5e4+3;
int n,m;
int Etot=-1;
int head[M],nxt[M];
struct Edge{
	int u,v;
	bool cut;
}to[M];
inline void join(int u,int v){
	nxt[++Etot]=head[u];
	head[u]=Etot;
	to[Etot]={u,v,0};
}
int idx;
int dfn[N],low[N];
void dfs1(int u,int f){
	dfn[u]=low[u]=++idx;
	for(int i=head[u];~i;i=nxt[i]){
		int v=to[i].v;
		if(!dfn[v]){
			dfs1(v,u);
			low[u]=std::min(low[u],low[v]);
			if(dfn[u]<low[v]){
				to[i].cut=to[i^1].cut=1;
			}
		}else if(v!=f){
			low[u]=std::min(low[u],dfn[v]);
		}
	}
}
int Ctot,col[N];
void dfs2(int u,int f){
	col[u]=Ctot;
	for(int i=head[u];~i;i=nxt[i]){
		int v=to[i].v;
		if(v==f or to[i].cut) continue;
		if(col[v]) continue;
		dfs2(v,u);
	}
}
int main(){
	filein(a);fileot(a);
	read(n,m);
	memset(head,-1,sizeof(head) );
	for(int i=1;i<=m;++i){
		int u,v;
		read(u,v);
		join(u,v);join(v,u);
	}
	idx=0;
	for(int i=1;i<=n;++i){
		if(!dfn[i]){
			dfs1(i,i);
		}
	}
	for(int i=1;i<=n;++i){
		if(!col[i]){
			++Ctot;
			dfs2(i,i);
		}
	}
	printf("%d\n",Ctot);
	return 0;
}

模板

例題

點雙連通

不難發現,一個點雙連通分量是被一個割點所分開,並且這個割點也在點雙連通分量內。我們可以像維護強連通分量一樣,將點入棧。我們在檢驗一個點是否為割點時,記當前點為 \(u\),目標點為 \(v\) ,此時 \(low[v]<=dfn[u]\) , 就直接瘋狂彈出棧,直到彈出目標點 \(v\),把彈出的點全部存入一個點雙連通分量,再把 \(u\) 加入即可。

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout);
#define Better_IO 1
namespace IO{
	inline bool blank(const char &c){
		return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
	}
	#if Better_IO==true
		char buf[(1<<20)+3],*p1(buf),*p2(buf);
		char buf2[(1<<20)+3],*p3(buf2);
		const int lim=1<<20;
		inline char gc(){
			if(p1==p2) p2=(p1=buf)+fread(buf,1,lim,stdin);
			return p1==p2?EOF:*p1++;
		}
		#define pc putchar
	#else
		#define gc getchar
		#define pc putchar
	#endif
	inline void gs(char *s){
		char ch=gc();
		while(blank(ch) ) {ch=gc();}
		while(!blank(ch) ) {*s++=ch;ch=gc();}
		*s=0;
	}
	inline void gs(std::string &s){
		char ch=gc();s+='#';
		while(blank(ch) ) {ch=gc();}
		while(!blank(ch) ) {s+=ch;ch=gc();}
	}
	inline void ps(char *s){
		while(*s!=0) pc(*s++);
	}
	inline void ps(const std::string &s){
		for(auto it:s) 
			if(it!='#') pc(it);
	}
	template<class T>
	inline void read(T &s){
		s=0;char ch=gc();bool f=0;
		while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
		while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
		if(ch=='.'){
			db p=0.1;ch=gc();
			while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
		}
		s=f?-s:s;
	}
	template<class T,class ...A>
	inline void read(T &s,A &...a){
		read(s);read(a...);
	}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=5e2+3;
const int M=2e4+3;
int Etot;
int head[N],nxt[M];
struct Edge{
	int u,v;
}to[M];
inline void join(int u,int v){
	nxt[++Etot]=head[u];
	head[u]=Etot;
	to[Etot]={u,v};
}
int n,m;
int dfn[N],low[N],idx;
int sta[N],top;
int Ctot;
std::vector<int>col[N];
inline void dfs1(int u,int f){
	dfn[u]=low[u]=++idx;
	sta[++top]=u;
	for(int i=head[u];~i;i=nxt[i]){
		int v=to[i].v;
		if(!dfn[v]){
			dfs1(v,u);
			low[u]=std::min(low[u],low[v]);
			if(low[v]>=dfn[u]){
				++Ctot;
				while(1){
					col[Ctot].push_back(sta[top]);
					if(sta[top]==v){
						--top;
						break;
					}
					--top;
				}
				col[Ctot].push_back(u);
			}
		}else if(v!=f){
			low[u]=std::min(low[u],dfn[v]);
		}
	}
}
int main(){
	filein(a);fileot(a);
	read(n,m);
	Etot=-1;
	memset(head,-1,sizeof(head) );
	for(int i=1;i<=m;++i){
		int u,v; read(u,v);
		join(u,v);join(v,u);
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i]){
			dfs1(i,i);
		}
	}
	for(int i=1;i<=Ctot;++i){
		for(auto it:col[i]){
			printf("%d ",it);
		}putchar('\n');
	}
	return 0;
}

例題

參考資料

寒假2019培訓:雙連通分量(點雙+邊雙)

OI-wiki 雙連通分量