POJ 3177&& 3352
题意:添加一定数目的边,构成无向双连通图
方法:一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
low[]值相同的表示在一个双连通子图中,所有可以利用这个进行缩点。然后统计度为1的结点的数目就是leaf数
渣代码:
View Code
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #define REP(i, n) for(i = 0; i < n; ++i) #define FOR(i, L, H) for(i = L; i <= H; ++i) #define FORD(i, H, L) for(i = H; i >= L; --i) #define CL(arr, val) memset(arr, val, sizeof(arr)) using namespace std; const int N = 5024; struct node { int to; int next; } g[N*100]; int head[N]; int dfn[N]; int low[N]; int out[N]; int t, cnt, top, ind; void init() { CL(head, -1); CL(dfn, 0); CL(low, 0); CL(out, 0); t = cnt = top = ind = 0; } void add(int u, int v) { g[t].to = v; g[t].next = head[u]; head[u] = t++; } void tarjan(int u, int pre) { dfn[u] = low[u] = ++ind; int i, v; bool flag = true; for(i = head[u]; i != -1; i = g[i].next) { v = g[i].to; if(v == pre && flag) {flag = false; continue;} //考虑重边 if(!dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]); } else low[u] = min(low[u], dfn[v]); } } int main() { //freopen("data.in", "r", stdin); int n, r, i, u, v, ans; while(~scanf("%d%d", &n, &r)) { init(); while(r--) { scanf("%d%d", &u, &v); add(u, v); add(v, u); } FOR(i, 1, n) if(!dfn[i]) tarjan(i, -1); FOR(u, 1, n) { for(i = head[u]; i != -1; i = g[i].next) { v = g[i].to; if(low[u] != low[v]) { out[low[u]] ++; } } } ans = 0; FOR(i, 1, ind) { if(out[i] == 1) ++ans; } if(ans == 1) printf("0\n"); else printf("%d\n", (ans + 1)/2); } return 0; }