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.注意度的计算,由于在此处,仅标记了一条边为桥,而未标识其反向边,因此计算度的时候需要对两个点的度都操作。