`
yzmduncan
  • 浏览: 330319 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

无向图——双连通分量

J# 
阅读更多

    双连通图:在无向图连通图中,如果删除该图中的任意一点和依附它的边,不改变图的连通性,则称该图为双连通的无向图。

    由上述定义可知,双连通分量中,每两个结点之间至少有两条不同的路径可以相互到达。

    割点:在无向连通图中删去某个点a和依附a的边,图变为不连通,则该点称为割点,也叫关节点。

    割边:在无向连通图中删去某条边,图变为不连通,则该边称为割边,也叫桥。

    点双连通分支(块)与边双连通分支:

点双连通分支与边双连通分支是两个完全不同的概念。割点可以存在多个点连通分支中(相反,桥就不一样)。一个图可以有割点而没有割边,也可以有割边而没有割点。

 

点双连通分支的求法和边双连通分支的求法类似,不过在出栈的地方有些不同。下面介绍几个例子。

 

POJ3177(3352)

题目大意:最少需要加多少条边使得原图变为双连通图(原图连通)。

解:求桥(注意平行边),在求桥的过程中缩点,利用并查集,将每个连通分量用一个点代表。将这些代表用桥连接起来,就构成了一颗树。统计树中度数为1的点(即叶子结点)的个数count,将叶子结点两两相连,则添加边的数量为(count+1)/2。

#include <iostream>
const int MAX = 5002;

int p[MAX];
struct Graph
{
	int to;
	int next;
}e[MAX*4];
int index[MAX];
int edgeNum;
int seq;

int low[MAX];		//low[u]表示在树中从u点出发,经过一条其后代组成的路径和回退边,所能到达的最小深度的顶点标号
int dfn[MAX];		//dfn[u]表示结点u在树中的编号
int bridge[MAX][2],bridge_n;
int degree[MAX];
int n,m;

int min(int x, int y)
{
	return x < y ? x : y;
}

void makeSet()
{
	for(int i = 1; i <= n; i++)
		p[i] = i;
}

int findSet(int x)
{
	if(x != p[x])
		p[x] = findSet(p[x]);
	return p[x];
}

void Union(int x, int y)
{
	x = findSet(x);
	y = findSet(y);
	if(x == y)
		return;
	p[y] = x;
}

void addEdge(int from, int to)
{
	e[edgeNum].to = to;
	e[edgeNum].next = index[from];
	index[from] = edgeNum++;
	e[edgeNum].to = from;
	e[edgeNum].next = index[to];
	index[to] = edgeNum++;
}

//边连通分量,求桥
void bridge_dfs(int u, int v)
{
	int repeat = 0;					//有平行边
	low[u] = dfn[u] = seq++;
	for(int i = index[u]; i != -1; i = e[i].next)
	{
		int w = e[i].to;
		if(w == v)
			repeat++;
		if(dfn[w] < 0)
		{
			bridge_dfs(w,u);
			low[u] = min(low[u],low[w]);
			if(!(low[w] > dfn[u]))		//不是桥,缩点
			{
				Union(w,u);
			}
			else
			{
				bridge[++bridge_n][0] = u;
				bridge[bridge_n][1] = w;
			}
		}
		else if(v != w || repeat != 1)    //重要
			low[u] = min(low[u],dfn[w]);
	}
}

int solve()
{
	int i,j;
	int a,b;
	int count = 0;
	memset(degree,0,sizeof(degree));
	for(i = 1; i <= bridge_n; i++)
	{
		a = findSet(bridge[i][0]);
		b = findSet(bridge[i][1]);
		degree[a]++;
		degree[b]++;
	}
	for(i = 1; i <= n; i++)
		if(degree[i] == 1)
			count++;
	return (count+1)/2;
}

int main()
{
	int i,j;
	int a,b;
	edgeNum = 0;
	seq = 0;
	bridge_n = 0;
	memset(index,-1,sizeof(index));
	memset(dfn,-1,sizeof(dfn));
	scanf("%d %d",&n,&m);
	for(i = 0; i < m; i++)
	{
		scanf("%d %d",&a,&b);
		addEdge(a,b);
	}
	makeSet();
	bridge_dfs(1,-1);
	printf("%d\n",solve());
	return 0;
}

 

POJ2942

题目大意:有n个骑士,骑士一段时间要坐在圆桌上举行高级会议,但要满足条件:互相憎恨的骑士不能相邻,圆桌上的人数必须是大于1的奇数。现在给出骑士之间的憎恨关系,问至少有多少个骑士要被排除在外。

解:首先建补图,这样骑士a和骑士b之间有连线,说明a和b可以相邻。还可以想到奇环,不在任何奇环的骑士将被排除。问题是如何求图中的奇环?这里有一个定理:双连通分量中如果存在奇环,那么整个分量的点全部包含在奇环中(自己体会)。这样,可以先求点的双连通分量,判断每个双连通分量是否包含奇环(用染色,若相邻点颜色相同,存在奇环),若存在奇环,则该分量包含的骑士都不会被排除。

#include <iostream>
const int MAX = 1001;
int n,m;
//若某块(双连通分量)不可染色为二分图,则该块存在奇圈;若某块存在奇圈,那么该块中的所有点都存在与奇圈中;
//那么答案就是所有不在任何奇圈中的骑士的个数。
bool map[MAX][MAX];
int dfn[MAX],low[MAX],stack[MAX];
int top,seq,result;
bool b[MAX],used[MAX];
int color[MAX];

int min(int x, int y)
{
	return x < y ? x : y;
}

bool isOk(int v, int col)
{
	color[v] = col;
	for(int w = 1; w <= n; w++)
	{
		if(map[v][w])
		{
			if(b[w])
			{
				if(color[v] == color[w])	//相邻两点颜色相同,构不成二分图,含奇圈
					return true;
				if(color[w] == -1)
					isOk(w, col^1);
			}
		}
	}
	return false;
}

void dummy(int t, int *a)
{
	int i,j;
	memset(b,0,sizeof(b));	//b[i]=1表示结点i属于当前的双连通分量中
	for(i = 0; i < t; i++)
		b[a[i]] = true;
	for(i = 0; i < t; i++)
	{
		memset(color,-1,sizeof(color));
		if(isOk(a[i],1))
			break;
	}
	if(i < t)		//含奇圈
	{
		for(j = 0; j < t; j++)
		{
			if(!used[a[j]])
			{
				result++;
				used[a[j]] = true;
			}
		}
	}
}

void bicon(int u)
{
	int a[MAX];
	low[u] = dfn[u] = seq++;
	stack[top] = u;
	top++;
	for(int w = 1; w <= n; w++)
	{
		if(map[u][w])
		{
			if(dfn[w] < 0)					//第一种情况,w是新点
			{
				bicon(w);
				low[u] = min(low[u],low[w]);
				if(low[w] >= dfn[u])		//u割点(把割点留在栈中) 
				{
					int k = 1;
					a[0] = u;
					do
					{
						--top;
						a[k++] = stack[top];
					}while(stack[top] != w);
					dummy(k,a);
				}
			}
			else					//u,w是回边(w是u的祖先)
				low[u] = min(low[u],dfn[w]);
		}
	}
}

void block()
{
	for(int i = 1; i <= n; i++)
		if(dfn[i] < 0)
			bicon(i);
}

int main()
{
	int i,j;
	int a,b;
	while(true)
	{
		scanf("%d %d",&n,&m);
		if(n == 0 && m == 0)
			break;
		memset(map,1,sizeof(map));
		memset(dfn,-1,sizeof(dfn));
		memset(used,0,sizeof(used));
		seq = 0;
		top = 0;
		result = 0;
		for(i = 0; i < m; i++)
		{
			scanf("%d %d",&a,&b);
			map[a][b] = false;
			map[b][a] = false;
		}
		for(i = 1; i <= n; i++)
			map[i][i] = false;
		block();
		printf("%d\n",n - result);
	}
	return 0;
}

 

POJ3694

题目大意:给定一个初始的网络,每次(1000次)向网络里加一条边,问网络中桥的数量。(网络是动态的)

解:这题难就难在网络是动态的,如果是静态,可以用边的双连通分量来直接求解。简单的想法是每修改一次就重新计算一次,但是这样超时。联想:通过缩点,缩点之间用桥连接,形成一颗树,树边就是桥,桥的总数为sum。每次向网络里加一条边a,b,先用并查集找出a和b所属的树的结点,显然,a和b到ab的最近公共祖先这条路径上的桥全部无效。这样,每次只需在树上操作sum--。这样复杂度就降下来了。

#include <iostream>
const int MAX = 100002;
int n,m;
int result;
struct Edge
{
	int to;
	int next;
}e[MAX*10],tree[MAX*10];
int index[MAX],index2[MAX],edgeNum,edgeT;
int seq;

int low[MAX],dfn[MAX];
int p[MAX],res[MAX];
int level[MAX],pre[MAX];
bool vis[MAX],bridge[MAX];

int min(int x, int y)
{
	return x < y ? x : y;
}

void addEdge(int from, int to)
{
	e[edgeNum].to = to;
	e[edgeNum].next = index[from];
	index[from] = edgeNum++;
}

void addTree(int from, int to)
{
	tree[edgeT].to = to;
	tree[edgeT].next = index2[from];
	index2[from] = edgeT++;
}

void makeSet()
{
	for(int i = 1; i <= n; i++)
		p[i] = i;
}

int findSet(int x)
{
	if(x != p[x])
		p[x] = findSet(p[x]);
	return p[x];
}

void Union(int x, int y)
{
	x = findSet(x);
	y = findSet(y);
	if(x == y)
		return;
	p[x] = y;
}

void bridge_dfs(int u, int v)
{
	int repeat = 0;
	low[u] = dfn[u] = seq++;
	for(int i = index[u]; i != -1; i = e[i].next)
	{
		int w = e[i].to;
		if(v == w)
			repeat++;
		if(dfn[w] < 0)
		{
			bridge_dfs(w,u);
			low[u] = min(low[u],low[w]);
			if(low[w] > dfn[u])
			{
				result++;
				res[result] = i;
				//bridge[w] = 1;
			}
			else
				Union(w,u);
		}
		else if(v != w || repeat != 1)
			low[u] = min(low[u],dfn[w]);
	}
}

void lca_dfs(int u, int deep)
{
	for(int i = index2[u]; i != -1; i = tree[i].next)
	{
		int v = tree[i].to;
		if(!vis[v])
		{
			vis[v] = true;
			pre[v] = u;
			level[v] = deep+1;
			lca_dfs(v,deep+1);
		}
	}
}

void lca(int u, int v)
{
	while(level[u] > level[v])
	{
		if(bridge[u])
		{
			result--;
			bridge[u] = 0;
		}
		u = pre[u];
	}
	while(level[v] > level[u])
	{
		if(bridge[v])
		{
			result--;
			bridge[v] = 0;
		}
		v = pre[v];
	}
	while(u != v)
	{
		if(bridge[u])
		{
			bridge[u] = 0;
			result--;
		}
		if(bridge[v])
		{
			bridge[v] = 0;
			result--;
		}
		u = pre[u];
		v = pre[v];
	}
}

int main()
{
	int i,j;
	int a,b;
	int q;
	int cases = 1;
	while(true)
	{
		scanf("%d %d",&n,&m);
		if(n == 0 && m == 0)
			break;
		edgeNum = 0;
		edgeT = 0;
		result = 0;
		seq = 0;
		memset(index,-1,sizeof(index));
		memset(index2,-1,sizeof(index2));
		memset(dfn,-1,sizeof(dfn));
		memset(vis,0,sizeof(vis));
		memset(level,0,sizeof(level));
		memset(bridge,0,sizeof(bridge));
		makeSet();
		for(i = 0; i < m; i++)
		{
			scanf("%d %d",&a,&b);
			addEdge(a,b);
			addEdge(b,a);
		}
		scanf("%d",&q);
		printf("Case %d:\n",cases++);
		bridge_dfs(1,-1);		//找到边的双连通 缩点
		int x,y;
		for(i = 1; i <= n; i++)	//将缩点集合转化为一颗树
		{
			for(j = index[i]; j != -1; j = e[j].next)
			{
				x = findSet(i);
				y = findSet(e[j].to);
				if(x != y)
					addTree(x,y);
			}
		}
		//
		memset(vis,0,sizeof(vis));
		vis[p[1]] = true;
		level[p[1]] = 1;
		lca_dfs(p[1],1);
		for(i = 1; i <= result; i++)
			bridge[findSet(e[res[i]].to)] = 1;
		while(q--)
		{
			scanf("%d %d",&a,&b);
			a = findSet(a);
			b = findSet(b);
			if(a != b)
				lca(a,b);
			printf("%d\n",result);
		}
		printf("\n");
	}
	return 0;
}

 

 

 

分享到:
评论

相关推荐

    图的遍历——计算连通分量个数

    要求采用邻接矩阵作为无向图的存储结构,邻接表作为有向图的存储结构,完成无向图和有向图的建立,并对建立好的图进行深度和广度优先遍历。具体实现要求: 1. 通过键盘输入图的顶点和边信息,分别构造一个无向图的...

    无向图生成森林

    在无向图的DFS过程中,每个顶点会被访问一次且仅一次,最终形成一种特殊的树结构——深度优先森林。在这个森林中,每个连通分量(即图中任意两个顶点都可以通过边到达的子图)都会被表示为一棵树。 在孩子兄弟链表...

    图论- 图的连通性- Tarjan 求双连通分量.rar

    在一个无向图中,如果删除任意一条边都不会导致图变为不连通,那么这个连通分量被称为双连通分量。换句话说,双连通分量中的任意两个顶点都存在至少两条互相独立的路径。双连通分量的研究在诸如网络可靠性、数据结构...

    算法刷题提高阶段-图论11

    双连通分量则是无向图中的一个子集,其中任意两个顶点都是通过一系列非桥边相连的,即使去掉这些边,分量内的顶点依然保持连通。理解双连通分量有助于我们解决许多实际问题,例如在互联网中分析节点的紧密程度,或者...

    数据结构——图(总结)

    连通分量是无向图中最大的子图,其中任意两个顶点都是连通的。强连通分量则是有向图中的极大子图,每个顶点都可以通过有向路径到达其他任何顶点。生成树是图的极小连通子图,包含所有顶点但只有一条路径,而生成森林...

    数据结构——图的有关操作

    一)建立一个无向图+遍历+插入 (1)以数组表示法作为存储结构,从键盘依次输入顶点数、弧数与各弧信息建立一个无向图; (2)对(1)中生成的无向图进行广度优先遍历并打印结果; (3)向(1)中生成的无向图插入一条...

    图论- 图的连通性.rar

    1. 连通图:如果在一个无向图中,从任一顶点出发可以到达其他所有顶点,那么这个图被称为连通图。连通图可以是简单连通图,即没有自环(一条边的两端为同一个顶点)和重边(两个顶点间有多条边)。例如,一棵树就是...

    数据结构——图PPT学习教案.pptx

    连通图是无向图中任何两个顶点间都存在路径的情况,其最大连通子图称为连通分量。对于有向图,强连通图是每个顶点对之间都存在双向路径的图,其最大强连通子图是强连通分量。 在图的操作上,常见的有建立和销毁图...

    北京师范大学数据结构教学资料第8章——图.ppt

    连通图是指在无向图中,任意两个顶点之间都存在路径的图,而连通分量是非连通图的最大连通子图。在有向图中,强连通图是每个顶点都能到达其他任何顶点的图,强连通分量是非强连通图的极大强连通子图。 生成树是连通...

    算法导论生成一个100个点3000条边的有向无环图实验1-4

    在有向图中,如果从每个顶点都能通过一系列边到达其他所有顶点,那么这些顶点组成一个强连通分量。在一个DAG中,可能存在多个强连通分量,也可能只有一个大的强连通分量或者完全没有强连通分量。找出这些分量对于...

    最大分量:调整网络矩阵并输出其最大连通分量中的节点列表-matlab开发

    最大连通分量是指在一个无向图中,能够找到的最大子集,其中任意两个节点之间都存在路径相连。在MATLAB中,可以使用内置函数`biconncomp`或`conncomp`来寻找这样的分量。 标题中提到的“最大分量”就是指最大连通...

    数据结构——图的实现

    根据边是否有方向,图可以分为有向图和无向图;根据边是否带有权重,图又可以分为加权图和无权图。在有向图中,边具有方向性,从一个顶点指向另一个顶点;而在无向图中,边没有方向,视为两个顶点之间的连接。 图的...

    图——基本概念和类型定义

    无向图 完全图 稀疏图 稠密图 权 网 邻接 关联(依附) 顶点的度 有向树 路径 路径长度 回路(环) 简单路径 简单回路(简单环) 连通图 强连通图 子图 连通分量 强连通分量 极小连通子图 生成树 生成森林 图的类型...

    (完整版)数据结构详细教案——图.pdf

    - **连通分量**:在无向图中,如果任意两个顶点都是可达的,则称图是连通的。否则,可分成若干个互不相连的子集,每个子集称为一个连通分量。 - **生成树**:无向图的生成树是图的一个子集,包含图中所有的顶点,...

    锦绣育才图的连通性PPT

    - **割点**: 对于一个无向图,如果删除一个节点后图的连通分量数增加,则称该节点为割点。 - **割边(桥)**: 如果删除一条边后图的连通性受到影响,即图分裂为两个不相连的子图,则称该边为割边。 - **连通性算法**...

    10 数据结构第六章 图知识点.docx

    * 邻接:在无向图中,如果两个顶点之间存在边,则称这两个顶点是邻接的。 * 依附:在无向图中,如果一个顶点和边相连,则称该顶点依附于该边。 * 无向图:在图中,所有边都是无向的。 * 有向图:在图中,所有边都是...

    数据结构Java图图基础及图遍历PPT学习教案.pptx

    - **连通分量**:无向图的最大连通子图。 7. **强连通图与强连通分量**: - **强连通图**:有向图中任意两个顶点间都存在双向可达的路径。 - **强连通分量**:有向图的最大强连通子图。 8. **生成树**: - **...

    ACM知识点——图论讲解

    无向图中的边没有方向,而有向图中的边有方向。加权图则为每条边赋予一个数值,代表边的权重或成本。 2. **图的表示**:在程序中,图可以使用邻接矩阵或邻接表来表示。邻接矩阵是二维数组,如果两个顶点之间有边,...

    Tarjan算法[收集].pdf

    除了求解强连通分量,Tarjan算法还有其他应用,例如在无向图中求解双连通分量。这种算法与求解强连通分量的Tarjan算法有着相似的结构和思想,通过类比和组合理解,可以帮助我们更深入地掌握这两种算法。 总的来说,...

Global site tag (gtag.js) - Google Analytics