连通分量模板

点-双连通分量:任意两点之间至少存在两条“点不重复”的路径。等价于内部无割点

边-双连通分量:任意两点之间至少存在两条“边不重复”的路径。等价于内部无桥

Tarjan

void dfs(int u){
	pre[u] = lowlink[u] = ++cnt;
	st.push(u);
	for(int i=0;i<G[u].size();i++){
		int v = G[u][i];
		if(!pre[v]){    //之前没有访问过
			dfs(v);
			lowlink[u] = min(lowlink[u],lowlink[v]);
		}else if(!sccno[v]){   //遇到之前访问过的了
			lowlink[u] = min(pre[v],lowlink[u]);
		}
	}
	if(pre[u] == lowlink[u]){
		scc++;
		while(1){
			int x = st.top();	st.pop();
			sccno[x] = scc;
			if(x == u)	break;
		}
	}
}
void Tarjan(){
	cnt = scc = 0;
	memset(pre,0,sizeof(pre));
	memset(sccno,0,sizeof(sccno));
	for(int i=1;i<=n;i++)
		if(!pre[i])	dfs(i);
}

Tarjan缩点

for(int i=1;i<=n;i++){
	for(int j=first[i];j;j=ne[j]){
		int v = to[j];
		if(sccno[i] != sccno[v]){	
			G[sccno[i]].push_back(sccno[v]);
			G[sccno[v]].push_back(sccno[i]);
		}
	}
}

Tarjan求桥

void dfs(int u,int fa){
	dfn[u] = lowlink[u] = ++cnt;
	st.push(u);
	int num = 0;
	for(int i=first[u];i;i=ne[i]){
		int v = to[i];
		if(v == fa){
			num++;
			if(num == 1)	continue;   //因为无向图有反向边,所以第一次搜到的不是重边。
		}
		if(!dfn[v]){
			dfs(v,u);
			lowlink[u] = min(lowlink[v],lowlink[u]);
			if(lowlink[v] > dfn[u])	bridge++;
		}else if(!sccno[v]){    
			lowlink[u] = min(lowlink[u],dfn[v]);
		}
	}
	if(dfn[u] == lowlink[u]){
		scc++;
		while(1){
			int x = st.top();	st.pop();
			sccno[x]= scc;
			if(x == u)	break;
		}
	}
}
void Tarjan(){
	cnt = scc = 0;
	memset(pre,0,sizeof(pre));
	memset(sccno,0,sizeof(sccno));
	dfs(1);    //桥的前提是无向连通图,所以dfs一次就遍历所有的点了。
}

Tarjan求割点

void dfs(int u){
	pre[u] = lowlink[u] = ++cnt;
	st.push(u);
	for(int i=0;i<G[u].size();i++){
		int v = G[u][i];
		if(!pre[v]){
			dfs(v);
			lowlink[u] = min(lowlink[u],lowlink[v]);
			if(lowlink[v] >= pre[u] && u != root)	ans[u] = true;    
			else if(u == root)	son++;    //只有son > 1是根节点内才是割点
		}else if(!sccno[v]){
			lowlink[u] = min(lowlink[u],pre[v]);
		}
	}
	if(pre[u] == lowlink[u]){
		scc++;
		while(1){
			int x = st.top();	st.pop();
			sccno[x] = scc;
			if(x == u)	break;
		}
	}
}
void Tarjan(){
	cnt = scc = 0;
	memset(pre,0,sizeof(pre));
	memset(sccno,0,sizeof(sccno));
	dfs(1);   //割点遍历一次
}

Kosaraju

void dfs(int u){
	vis[u] = true;
	for(int i=0;i<G1[u].size();i++){
		int v = G1[u][i];
		if(vis[v])	continue;
		vis[v] = true;
		dfs(v);
	}	
	s.push_back(u);    //反向记录拓扑序列
} 
void dfs2(int u){
	vis[u] = true;
	sccno[u] = scc;    //连通分块的编号
	for(int i=0;i<G2[u].size();i++){
		int v = G2[u][i];
		if(vis[v])	continue;
		dfs2(v);
	}
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		s.clear();
		for(int i=0;i<=n;i++)	G1[i].clear(),	G2[i].clear();
		for(int i=0;i<m;i++){
			int from,to;
			scanf("%d%d",&from,&to);
			G1[from].push_back(to);
			G2[to].push_back(from);
		}
		memset(vis,false,sizeof(vis));
		for(int i=1;i<=n;i++)
			if(!vis[i])		dfs(i);
		memset(vis,false,sizeof(vis));
		for(int i=n-1;i>=0;i--){
			if(vis[s[i]])	continue;
			scc++;	dfs2(s[i]);	
		}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值