算法刷题记录(Day 27)

Redundant Paths(poj 3177)

原题链接
题目类型:求解边双连通分量
tarjan算法求双连通分量之概念解释
tarjan算法求双连通分量代码实现

双连通分量的定义:
1.在一个无向图中,若任意两点间至少存在两条“点不重复”的路径,则说这个图是点双连通的(简称双连通,biconnected)。通俗来讲,是指不存在割点。在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量,Biconnected Component,BCC)。
2…在一个无向图中,若任意两点间至少存在两条“边不重复”的路径,则说这个图是边双连通的。通俗来讲,是指不存在桥。类似地,称为边双连通分量。

对于无向图,tarjan求解双连通分量的过程和tarjan求解有向图的强连通分量的过程是一样的,但是加入了to不能是from的父节点这个条件。同时,这个过程和tarjan求解桥的过程在本质上是一致的。
在本题中,需要添加的边数为缩点后的图中,(度为1的点的个数+1)/2,为了较为方便滴计算度,在缩点 后的图中,需要标识割边,这是用如下的两个条件都可以进行判别。

对于边(from,to) (from在dfs搜索树中是to的父亲节点)
1.dfn[to]==low[to]
2.low[to]>dfn[from]

#include<iostream>
#include<vector>
#include<cstring>
#include<stack>
#define FMAX 5500
using namespace std;
int F, R;
struct edge {
	int from, to, next;
	int is;//是否为割边
};
vector<edge> E(FMAX*2);
int h[FMAX];
int a, b;
void addline(int x, int y) {
	edge cur;
	cur.from = x, cur.to = y, cur.next = h[x], cur.is = 0;
	h[x] = E.size();
	E.push_back(cur);

	cur.from = y, cur.to = x, cur.next = h[y], cur.is = 0;
	h[y] = E.size();
	E.push_back(cur);
}

int dfn[FMAX], low[FMAX], belong[FMAX];
stack<int > S;
int tot = 0;
int connect = 0;
void tarjan(int x, int fa) {
	dfn[x] = low[x] = ++tot;
	S.push(x);
	for (int i = h[x]; i != -1; i = E[i].next) {
		int to = E[i].to;
		if (!dfn[to]) {
			tarjan(to, x);
			low[x] = min(low[x], low[to]);
			//用来判断是否为割边
			if (low[to] == dfn[to]) E[i].is = 1;
		}
		else if (to != fa) low[x] = min(low[x], dfn[to]);
	}
	if (dfn[x] == low[x]) {
		int cur = -1;
		while (cur!=x) {
			cur = S.top();
			S.pop();
			belong[cur] = connect;
		}
		connect++;
	}

}

int du[FMAX];
int main() {
	cin >> F >> R;
	memset(h, -1, sizeof(h));
	for (int i = 0; i < R; i++) {
		cin >> a >> b;
		addline(a, b);
	}

	memset(dfn, 0, sizeof(dfn));
	for (int i = 1; i <= F; i++) {
		if (!dfn[i]) tarjan(i, 0);
	}

	memset(du, 0, sizeof(du));
	for (int i = 0; i < E.size(); i++) {
		//缩点后只有割边才是缩点后的边
		if (E[i].is) {
			//注意若a是割边,其反向边b未被标识为割边
			du[belong[E[i].from]]++;
			du[belong[E[i].to]]++;
		}
	}
	int res = 0;
	for (int i = 0; i < connect; i++) {
		if (du[i] == 1) res++;
	}
	cout << (res + 1) / 2;
}

tip:
1.注意度的计算,由于在此处,仅标记了一条边为桥,而未标识其反向边,因此计算度的时候需要对两个点的度都操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值