连通图总结

**~~

连通图总结

**~~

有向图

强连通分量:
强连通:有向图中两个结点如果能两两互相到达,就称这两个点强连通。
强连通图:有向图中所有结点都能两两到达,就称强连通图。
强连通分量:非强连通的有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。一个图中可以有多个。
强连通分量是一个环或者一个点。
模板

const int MAX_N=1e5+5;
const int MAX_E=3e5+5;
struct Edge{ int to,nxt; }e[MAX_E];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
struct Data_Edge{ int u,v; }de[MAX_E];//数据边

int idx,dfn[MAX_N],low[MAX_N];//dfn时间戳 low一个点及其子树的点可追溯到的最早的时间戳
int belong[MAX_N],num[MAX_N],scc;//belong 一个点属于哪个scc,num 一个scc的大小,scc总scc数
int Stack[MAX_N],top;
bool in_stack[MAX_N];
void tarjan(int u)
{
	dfn[u]=low[u]=++idx;
	Stack[++top]=u,in_stack[u]=true;
	reps(u){
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);	
		else if(in_stack[v])	low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		belong[u]=++scc,in_stack[u]=false,num[scc]=1;
		while(Stack[top]!=u) belong[Stack[top]]=scc,in_stack[Stack[top--]]=false,num[scc]++;
		top--;
	}
}
int in[MAX_N],out[MAX_N];
vector<int> g[MAX_N];//缩点后的新图
void condensation_point(int n,int m)//点数 边数
{
	repi(i,1,n) in[i]=out[i]=0,g[i].clear();
	repi(i,1,n)if(!dfn[i])	tarjan(i);
	if(scc==1)	return;
	repi(i,1,m){
		int u=de[i].u,v=de[i].v;
		if(belong[u]!=belong[v]) out[belong[u]]++,in[belong[v]]++,g[belong[u]].pb(belong[v]);
	}
}

void init(int n)
{
	repi(i,1,n)	dfn[i]=in_stack[i]=head[i]=0;
	tote=idx=scc=top=0;
}
/*
	int n,m; si(n),si(m); init(n);
	repi(i,1,m){
		int u,v; si(u),si(v);
		add_edge(u,v),de[i]={u,v};
	}
	condensation_point(n,m);
*/

2-SAT
连边模型:
记x为选,no(x)/x’为不选
模型一:两者(A,B)不能同时取
  那么选择了A就只能选择B’,选择了B就只能选择A’
  连边A→B’,B→A’

模型二:两者(A,B)不能同时不取(至少选一个)
  那么选择了A’就只能选择B,选择了B’就只能选择A
  连边A’→B,B’→A

模型三:两者(A,B)要么都取,要么都不取
  那么选择了A,就只能选择B,选择了B就只能选择A,选择了A’就只能选择B’,选择了B’就只能选择A’
  连边A→B,B→A,A’→B’,B’→A’

模型四:两者(A,A’)必取A
  连边A’→A
建图后跑SCC,若x与no(x)在同一scc则无解,反之有解。
模板

#define no(x) ((x)+n)//选1-n 不选n+1-2*n
struct Edge{int to,nxt;}e[MAX_E];//边数上界根据题目估算
int head[MAX_N],tote;//点数上界*2
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}

int idx,dfn[MAX_N],low[MAX_N];//dfn??? low????????????????????
int belong[MAX_N],num[MAX_N],scc;//belong ???????scc,num ??scc???,scc?scc?
int Stack[MAX_N],top;
bool in_stack[MAX_N];
void tarjan(int u)
{
	dfn[u]=low[u]=++idx;
	Stack[++top]=u,in_stack[u]=true;
	reps(u){
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);	
		else if(in_stack[v])	low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		belong[u]=++scc,in_stack[u]=false,num[scc]=1;
		while(Stack[top]!=u) belong[Stack[top]]=scc,in_stack[Stack[top--]]=false,num[scc]++;
		top--;
	}
}
int ans[MAX_N];
void SAT(int n)
{
	repi(i,1,2*n)if(!dfn[i]) tarjan(i);
    repi(i,1,n){
    	int x=belong[i],y=belong[no(i)];
   		if(x==y){puts("-1");return;}//无解
		if(x<y) ans[++ans[0]]=i;//选入 ans 1-ans[0]存解
	}
}
void init(int n)
{
	repi(i,1,n)	dfn[i]=in_stack[i]=head[i]=0;
	tote=idx=scc=top=ans[0]=0;
}
/*
init(2*n) SAT(n);
建立强连通分量时,按正向连边顺序,从第一个点,也就是选s开始的点搜 那么对于每一组,先被并入一个scc的肯定时选s开始的,故
直接通过比较每对scc的大小即可做出选择。
*/

(还有个构造解的是重建图,反边染色,比这个麻烦,感觉没啥意义就没管了)
DFS找字典序最小解

#define no(x) (x^1) //反点按具体情况 以下以0-1 2-3...分组为例
struct Edge{int to,nxt;}e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
bool mark[MAX_N];//mark为true表示这个点选入  例子中点下标i 0-2n-1
int top,Stack[MAX_N];
bool dfs(int u)
{
	if(mark[no(u)])	return false;
	if(mark[u])	return true;
	Stack[++top]=u,mark[u]=true;
	reps(u)if(!dfs(v))	return false;
	return true;
}
bool check(int n)
{
	for(int i=0;i<2*n;i+=2)if(!mark[i]&&!mark[no(i)]){
		top=0;
		if(!dfs(i)){
			while(top)	mark[Stack[top--]]=false;
			if(!dfs(no(i)))	return false;
		}
	}
	return true;
}
void init(int n)
{
	repi(i,0,n)	head[i]=mark[i]=0;
	tote=0;
}
/*
init(2*n),check(n)
true有解 
*/

无向图

割点与桥
割点与桥(割边)的定义
在无向图中才有割边和割点的定义
割点:无向连通图中,去掉一个顶点及和它相邻的所有边,图中的连通分量数增加,则该顶点称为割点。
桥(割边):无向联通图中,去掉一条边,图中的连通分量数增加,则这条边,称为桥或者割边。
割点与桥(割边)的关系:
(1)有割点不一定有桥,有桥一定存在割点
(2)桥一定是割点依附的边。
模板

/*
求 无向图的割点和桥 
*/ 
struct Edge{ int to,nxt;bool cut; }e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u],e[i].cut=false;head[u]=tote;}

int rt,idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
bool cut[MAX_N];//标记一个点是不是割点
int add_block[MAX_N],bridge;//add_block删除一个点增加的连通块数,bridge桥数
void tarjan(int u,int in_edge)
{
	low[u]=dfn[u]=++idx;
	int son=0;
	reps(u)if(i!=(in_edge^1)){//不走反向边
		if(!dfn[v]){
			son++,tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]) bridge++,e[i].cut=e[i^1].cut=true;//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且dfn(u)
			if(u!=rt&&low[v]>=dfn[u]) cut[u]=true,add_block[u]++;//一个顶点u是割点,当且仅当满足(1)u为树根,且u有多于一个子树。
			//或(2) u不为树根,且满足存在(u,v)为树枝边,使得dfn(u)<=low(v) 	
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(u==rt&&son>1) cut[u]=true,add_block[u]=son-1;
}
void init(int n)
{
	repi(i,1,n) head[i]=dfn[i]=add_block[i]=cut[i]=0;
	tote=1,idx=bridge=0;
}
/*
注意加边的时候原边与反边一起加
repi(i,1,n)if(!dfn[i]) rt=i,tarjan(i,1);
*/

双连通分量
双连通分量:有点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。

点双连通和边双连通

点双连通:删掉一个点之后,图仍联通。任意两点间至少存在两条“点不重复”的路径。在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量,Biconnected Component,BCC)

建立一个栈,存储当前 双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满 足 DFS(u)<=Low(v),说明 u 是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与 其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一 个点双连通分支。

边双连通:删掉一条边之后,图仍联通。任意两点间至少存在两条“边不重复”的路径。总而言之就是一个圈,正着走反着走都可以相互到达,至少只有一个点。

性质
1.若BCC间有公共点,则公共点为原图的割点
2.无向连通图中割点一定属于至少两个BCC,非割点只属于一个BCC
3.在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通 。
4.在一张连通的无向图中,对于两个点u和v,如果无论删去哪个点(只能删去一个,且不能删 和 自己)都不能使它们不连通,我们就说u和v点双连通 。
5.边双连通具有传递性,即,若x,y边双连通, y,z边双连通,则x,z边双连通。
6.点双连通 不 具有传递性,反例如下图, A,B点双连通, B,C点双连通,而 A,C不点双连通。

一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边, 剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这 个图一定是一棵树,边连通度为 1。 统计出树中度为 1 的节点的个数,即为叶节点的个数,记为 leaf。则至少在树上添加(leaf+1)/2 条边,就能 使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两 个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一 定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2 次, 把所有点收缩到了一起。

模板
点双连通分量

struct Edge{ int to,nxt; }e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}

int rt,idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
int Stack[MAX_N],top;
int belong[MAX_N],block;//belong 每个点属于哪个bcc,block:bcc数
bool cut[MAX_N];//标记一个点是不是割点
void tarjan(int u,int in_edge)
{
	low[u]=dfn[u]=++idx;
	Stack[++top]=u;
	int son=0;
	reps(u)if(i!=(in_edge^1)){
		if(!dfn[v]){
			son++,tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(dfn[u]<=low[v]){
				cut[u]=true,block++;
				int vn; tmp[0]=0;
				do{
					vn=Stack[top--],belong[vn]=block;
				}while(vn!=v);//此处不用while写 因为存在u连接一个割点的时候,那个割点不会被从栈中弹出,导致栈中v下面实际不是u
			}
		}
		else if(low[u]>dfn[v])	low[u]=dfn[v];
	}
	if(u==rt&&son<2) cut[u]=false;
}

void init(int n)
{
	repi(i,1,n) head[i]=dfn[i]=belong[i]=cut[i]=0;
	idx=top=block=0,tote=1;
}
/*
注意加边的时候原边与反边一起加
repi(i,1,n)if(!dfn[i]) rt=i,tarjan(i,1);
*/

边双连通分量

struct Edge{ int to,nxt; bool cut;}e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u],e[tote].cut=false;head[u]=tote; }
vector<int> g[MAX_N];
int idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
int belong[MAX_N],dcc;//belong 每个点属于哪个dcc,dcc:dcc数
int bridge;

void tarjan(int u,int in_edge)
{
	low[u]=dfn[u]=++idx;
	reps(u)if(i!=(in_edge^1)){
		if(!dfn[v]){
			tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]) bridge++,e[i].cut=e[i^1].cut=true;
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
void dfs_dcc(int u)
{
	belong[u]=dcc;
	reps(u)if(!e[i].cut&&!belong[v]) dfs_dcc(v);
}
void init(int n)
{
	repi(i,1,n) head[i]=dfn[i]=belong[i]=0,g[i].clear();
	idx=bridge=dcc=0,tote=1;
}
/*
	repi(i,1,n)if(!dfn[i]) tarjan(i,1);
	repi(i,1,n)if(!belong[i]) dcc++,dfs_dcc(i);
	repi(u,1,n)reps(u)if(belong[u]!=belong[v]) g[belong[u]].pb(belong[v]);
	//g为缩点后的图 最好换成链式前向星
*/

你可能感兴趣的:(图论)