`
yzmduncan
  • 浏览: 326670 次
  • 性别: 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. 通过键盘输入图的顶点和边信息,分别构造一个无向图的...

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

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

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

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

    leetcodepremium是什么-myProjects:个人iOS项目

    leetcode ...无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表 - 检测链表中的循环

    lrucacheleetcode-leetcode:力码解决方案

    lru缓存leetcode leetcode 力码解决方案 ...无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表

    leetcodepremium-coding_practice:我自己对Leetcode和其他编码挑战问题的解决方案

    leetcode 溢价编码实践 练习的顶级编码问题 ...无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表 -

    最大公共字符串leetcode-lc:液晶显示器

    最大公共字符串leetcode leetcode 关键词: interview 最长回文子串硬币问题 目标 大批 O ...无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetco

    leetcode同类骰子-Algorithms:LeetCode,GeeksforGeeks

    323,无向图中连通分量的数量,中等 444,序列重建,中 第二周——堆 215,数组中的第 K 个最大元素 218,天际线问题 239,滑动窗口最大值 253, 会议室二 (252) 264、丑数二(263、313) 295,从数据流中查找中值 ...

    数据结构习题答案(全部算法)严蔚敏版

    7.7.1 无向图的邻接表的建立和遍历 7.7.2 有向无环图的拓扑排序和求关键路径 习题七 第8章 查找 8.1 基本概念 8.2 静态表查找 8.2.1 顺序表的查找 8.2.2 有序表的查找 8.2.3 索引顺序表的查找 8.3 动态查找...

    leetcode链表删除环-practice_on_DS_and_ALGO:实践_on_DS_and_ALGO

    在无向图中 检测有向图中的循环 在图表中 在图中查找强连通分量 准备工作 一旦您对上述数据结构和算法感到满意,请多次(至少 2-3 次)进行以下练习,直到您可以闭上眼睛进行练习。 从头开始实现一个 ArrayList 链表...

    算法概论.pdf

    3.2Depth-firstsearchinundirectedgraphs(无向图中的深度优先搜索) 3.3Depth-firstsearchindirectedgraphs(有向图中的深度优先搜索) 3.4Stronglyconnectedcomponents(强连通分量) Exercises(习题)—— 4...

    用c描述的数据结构演示软件

    (2)求有向图的强连通分量(Strong_comp) (3)有向无环图的两个算法  拓扑排序(Toposort)  关键路径(Critical_path) (4)求最小生成树  普里姆算法(Prim)  克鲁斯卡尔算法(Kruscal) (5)求关节点和重...

    数据结构演示软件

    (2)求有向图的强连通分量(Strong_comp) (3)有向无环图的两个算法  拓扑排序(Toposort)  关键路径(Critical_path) (4)求最小生成树  普里姆算法(Prim)  克鲁斯卡尔算法(Kruscal) (5)求关节点...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    *5.6 C++处理字符串的方法——字符串类与字符串变量 5.6.1 字符串变量的定义和引用 5.6.2 字符串变量的运算 5.6.3 字符串数组 5.6.4 字符串运算举例 习题 第6章 指针 6.1 指针的概念 6.2 变量与指针 6.2.1 定义...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    *5.6 C++处理字符串的方法——字符串类与字符串变量 5.6.1 字符串变量的定义和引用 5.6.2 字符串变量的运算 5.6.3 字符串数组 5.6.4 字符串运算举例 习题 第6章 指针 6.1 指针的概念 6.2 变量与指针 6.2.1 定义...

    c语言数据结构算法演示(Windows版)

    图示窗口的上半部分显示无向图,下半部分自上而下分别显示 Vexnum、Vexdata、Visited、Low、Squlow(求得 low 值的顺序)和 artpoint(关节点)的信息。 25. 有向图的拓扑排序 图示窗口由5部分组成。其中左上显示有...

Global site tag (gtag.js) - Google Analytics