POJ 3352 Road Construction (边双连通分量)

题目类型  边双连通分量

题目意思
给出一个 n 个点 m 条边的无向连通图(n,m<=1000) 问至少要添加多少条无向边使得 删除原图中任意一条边后原图依然连通

解题方法
根据题意 显然如果删除原图中的 桥 就会使原图不连通 那么首先把原图中的 边双连通分量看成一个点(即缩点) 然后点与点之间的边即为桥
则原问题转化成给出一棵若干个点的树问把树对应的图变为一个边双连通图最少需要添加多少条边
具体方法 ->  Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)
结果为树的叶子结点数 + 1 再除 2

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>

using namespace std;

const int maxn = 1000 + 10;

struct Edge {
  int u, v;
};

int pre[maxn], dfs_clock, bcc_cnt; 
int bcc[maxn], d[maxn];
int GG[maxn][2], n;
bool g[maxn][maxn];

int dfs(int u, int fa) {
	int lowu = pre[u] = dfs_clock++;
	for( int i=0; i<n; i++ ) {
		if(g[u][i] == false) continue;
		int v = i;
		if(pre[v] == false) {
			int lowv = dfs(v, u);
			lowu = min(lowu, lowv);
			if(lowv > pre[u]) {
				g[u][v] = g[v][u] = false;
			}
		}
		else if(pre[v] < pre[u] && v != fa) {
			lowu = min(lowu, pre[v]);
		}
	}
	return lowu;
}

void find_cc() { // 用tarjan算法求出所有桥
  memset(pre, 0, sizeof(pre));
  dfs_clock = 0;
  for( int i=0; i<n; i++ ) {
    if(pre[i] == false) dfs(i, -1);
  }
}

void dfs(int u) { // 求不经过桥的连通分量 即边双连通分量
	bcc[u] = bcc_cnt;
	for( int i=0; i<n; i++ ) {
		if(g[u][i] == false || bcc[i] != -1) continue;
		dfs(i);
	}
}

int main() {
	freopen("in", "r", stdin);
	int m;
	while(scanf("%d%d", &n, &m) != EOF) {
		memset(g, 0, sizeof(g));
		for( int i=0; i<m; i++ ) {
			int u, v;
			scanf("%d%d", &u, &v);
			u--, v--;
			GG[i][0] = u, GG[i][1] = v;
			g[u][v] = g[v][u] = true;
		}
		find_cc();
		bcc_cnt = 0;
		memset(bcc, -1, sizeof(bcc));
		for( int i=0; i<n; i++ ) {
			if(bcc[i] == -1) dfs(i);
			bcc_cnt++;
		}
		memset(d, 0, sizeof(d));
		for( int i=0; i<m; i++ ) {
			int u = GG[i][0], v = GG[i][1];
			if(bcc[u] != bcc[v]) {
				d[bcc[u]]++; d[bcc[v]]++;
			}
		}
		int ans = 0;
		for( int i=0; i<bcc_cnt; i++ ) if(d[i] == 1) ans++; // 度数为1的点即为叶子结点
		printf("%d\n", (ans+1)/2);
	}
	return 0;
}

你可能感兴趣的:(图论,Tarjan,边双连通分量)