hihoCoder #1190 : 连通性·四(模板)

探讨无向图的点双连通子图概念,讲解如何计算点连通分量。文章指出,点双连通分量在删除任一点后仍保持图的连通性,最大点双连通子图即为点的双连通分量。通过分析割点的角色,得出点连通分量数量等于割点数加1,且根节点所在连通分量为最后一组点连通分量。
摘要由CSDN通过智能技术生成

#1190 : 连通性·四

题意:求多少个点连通分量。

对于一个无向图的子图,当删除其中任意一个点后,不改变图内点的连通性,这样的子图叫做点的双连通子图。而当子图的边数达到最大时,叫做点的双连通分量。

思路:每存在一个割点,就把一个区域一分为二。点的双连通分量就等于割点数量加1。对于分割到最后的图,最后一组点连通向量就是根节点所在的连通向量。(在这里想了好久,被hiho官方的题解误导,官方的题解是将割点分成两种情况讨论的,一种是根节点的情况,另外是非根结点的情况,实际统计的时候只会统计到非根结点分割图得出来的结果,对于最后一组点连通分量,因为图已经被分割成最后一个连通向量了,所以最后一组连通分量不再存在割点,即根结点所在的连通分量就是最后一组点连通分量)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 200000 + 10;
const int INF=int(1e9);
struct Edge {
	int u, v;
	int id;
	int next;
};
struct tarjan {     //tarjan模板
	int n;
	int dfn[maxn];    //dfn数组是个时间戳,就是访问那个节点的时间,也就是第几次调用dfs这个函数
	int low[maxn];    //Low是u的子节点能通过反向边到达的节点DFN的最小值, 初始值为dfn[u]
	int index;        //记录dfs调用的次数
	int edge[maxn];  //记录割边
	int node[maxn];   //记录割点
	int f[maxn];      //父亲节点
	int head[maxn];   //前向星的写法 
	bool vis[maxn];   //标记边是否访问过 
	int fa[maxn];     //每一条边属于的组的编号 
	int top;          //前向星记录边数 
	int tol;          //模拟存边的栈的下标移动 
	int cnt;          //点连通分量的个数 
	Edge e[maxn];     //前向星记录边数 
	int  es[maxn];    //模拟存边的堆栈 
	int temp[maxn];   //模拟队列,记录一个点连通分量的边 
	void init(int N) {       //初始化
		n = N;
		index = 0;
		top = 0;
		tol = 0;
		cnt = 0;
		memset(dfn, -1, sizeof(dfn));
		memset(f, 0, sizeof(f));
		memset(head, -1, sizeof(head));
		memset(vis, 0, sizeof(vis));
		memset(fa, -1, sizeof(fa));
	}
	void addedge(int u, int v,int i) {   //添加边
		e[top].u = u;
		e[top].v = v;
		e[top].id = i;
		e[top].next = head[u];
		head[u] = top++;
	}
	void dfs(int u) {       //dfs类似环缩点的写法,两个主要数组dfn[]和low[]
		dfn[u] = low[u] = ++index;
		bool flag = false;
		for (int i = head[u]; i != -1; i = e[i].next) {
			int v = e[i].v;
			int id = e[i].id;
			if (vis[id])
				continue;
			if (dfn[v] == -1) {
				f[v] = u;
				es[tol++] = id;
				dfs(v);
				low[u] = min(low[u], low[v]);
				if (low[v] >= dfn[u]) {   //low[v] >= dfn[u],表示u是一个割点 
					cnt++;                //为什么没有特判根节点呢?我个人理解为因为分割的图后,根节点所在连通分量即为一个点连通分量 
					int edg = -1;
					int Min=INF;
					int scnt=0;
					while (edg != id) {
						edg = es[--tol];
						Min=min(Min,edg);
						vis[edg] = 1;
						temp[scnt++]=edg;     //将边存在一个队列中再处理 
					}
					for(int i=0; i<scnt; i++)
						fa[temp[i]]=Min;
				}
			} else if (v != f[u]) {
				es[tol++] = id;
				low[u] = min(low[u], dfn[v]);
			}

		}
	}
	void solve() {   //遍历所有点
		for (int i = 1; i <= n; i++) {
			if (dfn[i] == -1)
				dfs(i);
		}
	}

} T;
int main() {
	int n, m;
	int u, v;
	scanf("%d %d", &n, &m);
	T.init(n);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &u, &v);
		T.addedge(u, v,i);
		T.addedge(v, u,i);
	}
	T.solve();
	cout << T.cnt << endl;
	for (int i = 1; i<=m; i++) {
		printf("%d%c", T.fa[i], i == m ? '\n' : ' ');
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值