Description
Input
Output
Sample Input
7 7
1 2
2 3
3 4
2 5
4 5
5 6
5 7
Sample Output
2
题目大意:直接抽象一下问题:给你一个无向连通图,计算最少需要添加多少边,才能使得任意两点之间至少有两条相互“边独立”的道路(即两条路径中没有相同的边)。
解题思路:这里要用到边双连通分量的知识,先解释一下:在边双连通分量中,不存在割边,其中任何一对顶点之间至少存在两条无公共边的路径(允许有公共内部顶点)。很容易看出,边连通分量中的所有点可以缩为一个点。这样原图就大大简化了,缩点之后的图中的边就只剩下桥了,然后统计出新生成的图(准确说应该是树)中的度为 1 的顶点个数sum ,运用结论(sum + 1)/ 2 就得到答案了。
Ps:缩点时要用到并查集。。
请看代码:
#include<iostream> #include<cstring> #include<string> #include<cmath> #include<cstdio> #include<vector> #include<algorithm> using namespace std ; const int MAXN = 5005 ; struct Node { int adj ; int e ; // 边的序号 Node *next ; } ; Node mem[MAXN * 2] ; // 边节点的数组 int memp ; // 统计边节点 Node *vert[MAXN] ; // 顶点指针数组 int set[MAXN] ; // 用于并查集 bool vis[MAXN] ; // 标记数组,记录顶点是否被访问过 bool vise[MAXN * 2] ; // 标记数组,记录边是否被访问过 int low[MAXN] ; // 记录顶点在深度优先搜索生成树中通过自己的子孙(如果有的话)以及一条回边 // 可以到达的最小深度 int dfn[MAXN] ; // 记录顶点在深度优先搜索生成树中所在的深度 int bridges[MAXN * 2][2] ; // 记录桥的两端顶点 int belong[MAXN] ; // 记录每个顶点所属的边连通分量 int d[MAXN] ; // 统计桥的两端顶点(缩点之后)的度 int cbridges ; // 记录原图中桥的个数 int tmpdfn ; int counte ; // 给图中的边编号 int sumfz ; // 统计原图中边连通分量个数 int root ; // 记录根节点 int n , m ; void clr() // 初始化 { memp = 0 ; counte = 0 ; memset(vis , 0 ,sizeof(vis)) ; memset(vert , 0 , sizeof(vert)) ; memset(vise , 0 , sizeof(vise)) ; memset(belong , -1 , sizeof(belong)) ; memset(bridges , -1 , sizeof(bridges)) ; memset(low , 0 , sizeof(low)) ; memset(dfn , 0 , sizeof(dfn)) ; } int find(int x) // 并查集(查找部分) { int r = x ; int t ; while (r != set[r]) { r = set[r] ; } /* while (x != set[x]) // 并查集的优化 , 可以加在程序中 { t = set[x] ; set[x] = r ; x = t ; }*/ return r ; } void unitset(int i , int j) // 并查集(合并部分) { int tx = find(i) ; int ty = find(j) ; if(tx < ty) { set[ty] = tx ; } else { set[tx] = ty ; } } void init() // 输入 { int i , j ; for(i = 0 ; i < m ; i ++) { int a , b ; scanf("%d%d" , &a , &b) ; //建图 mem[memp].adj = b ; mem[memp].e = counte ; mem[memp].next = vert[a] ; vert[a] = &mem[memp] ; memp ++ ; mem[memp].adj = a ; mem[memp].e = counte ++ ; mem[memp].next = vert[b] ; vert[b] = &mem[memp] ; memp ++ ; root = b ; } } void dfs(int u) // 找桥 { Node *p = vert[u] ; while (p != NULL) { int v = p -> adj ; int te = p -> e ; if(!vise[te]) // 图中可能有重边,所以应先判断此边是否被访问过 { vise[te] = 1 ; if(!vis[v]) { vis[v] = 1 ; dfn[v] = low[v] = ++ tmpdfn ; dfs(v) ; low[u] = min(low[u] , low[v]) ; if(low[v] > dfn[u]) // (u , v) 是桥 { bridges[cbridges][0] = u ; bridges[cbridges ++][1] = v ; } else // 如果(u,v)不是桥,那么u、v必在一个边连通分量中 { unitset(u , v) ; } } else { low[u] = min(low[u] , dfn[v]) ; } } p = p -> next ; } } int countfz() //统计边连通分支数(缩点) { int i ; int k ; int fz = 0 ; for(i = 1 ; i <= n ; i ++) { k = find(i) ; if(belong[k] == -1) { belong[k] = fz ++ ; } belong[i] = belong[k] ; // 缩点 } return fz ; } void solve() { int i ; for(i = 1 ; i <= n ; i ++) // 初始化并查集 { set[i] = i ; } tmpdfn = 1 ; cbridges = 0 ; vis[root] = 1 ; dfn[root] = low[root] = tmpdfn ; dfs(root) ; sumfz = countfz() ; memset(d , 0 , sizeof(d)) ; for(i = 0 ; i < cbridges ; i ++) // 统计各个边连通分量的度 { int ta = bridges[i][0] ; int tb = bridges[i][1] ; d[belong[ta]] ++ ; d[belong[tb]] ++ ; } int sumd1 = 0 ; for(i = 0 ; i < sumfz ; i ++) { if(d[i] == 1) sumd1 ++ ; } printf("%d\n" , (sumd1 + 1) / 2) ; // (度数为1的顶点个数 + 1)/ 2 即得答案 } int main() { while (scanf("%d%d" , &n , &m) != EOF) { clr() ; init() ; solve() ; } return 0 ; }