题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4612
题意:一个包含n个节点m条边的无向连通图(无自环,可能有重边)。求添加一条边后最少剩余的桥的数目。
思路:要想尽可能地消灭桥,那么添加的这条边一定是连通了最多的BCC。
所以首先进行双连通分量分解,并记录桥的数目;然后将同属一个BCC的点缩成一个,代之以block序号,以block序号为点将原图重构为一棵树。
最后求树的直径,桥的数目减去树的直径即为答案。
整体框架是学习了 http://www.cnblogs.com/kuangbin/archive/2013/07/25/3214879.html 的代码。一些细节是自己想的。
学习到的缩点的姿势:在BCC分解后,为每条边是否为桥打上了标记,同时belong数组已记录了每个点所属的连通块号(关节点每次不退栈,而归属于它所找到的最后一个BCC);这时遍历一遍所有点,如果点 u 所邻接的某条边 i 是桥,那么这条边一定是重构出树中的边,即这条边的终点为 v ,假如用vector<int>形的邻接表 G 存新图,此时应在u和v各自所属的连通块之间加一条边,即G[belong[u]].push_back(belong[v])。
学习到的处理重边的姿势:先把所有边按字典序排序,此时重边必然紧邻,所以addEdge时判一下相邻的边是否相同即可决定这条边是否打重边标记。
这里我用两重while循环、一个flagDup标记以及两个指针cur, i来处理,有点像尺取法:i 与 cur相同的话一直向前走,当遇到第一个 i != cur 时,通过判flagDup来决定当前从开始cur 到 i 之前的边是否打重边标记。开始在边界判断上出了点问题,注意内层循环要保证 i < m。
由于是无向图,同一条边在两个端点各登记一次,所以在边数组里有两个副本,但可以保证两条边的序号仅相差1,这样也可以通过与 1 异或方便地找到另一个副本。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <vector> 5 #define CLEAR(A, X) memset(A, X, sizeof(A)) 6 #define REP(N) for(int i=0; i<(N); i++) 7 #define REPE(N) for(int i=1; i<=(N); i++) 8 #define FREAD(FN) freopen((FN), "r", stdin) 9 #define pb(a) push_back(a) 10 11 using namespace std; 12 13 const int MAX_N = 200005; 14 const int MAX_M = 2000005; 15 16 struct Edge 17 { 18 int v, next; 19 bool isBrg; 20 bool more;//重边 21 }edges[MAX_M]; 22 23 int head[MAX_N], numE; 24 int low[MAX_N], dfn[MAX_N], S[MAX_N], belong[MAX_N]; 25 int clock, topS; 26 int block; 27 bool inStack[MAX_N]; 28 int numBrg; 29 30 void addEdge(int u, int v, bool isDup){ 31 edges[numE].v = v; 32 edges[numE].next = head[u]; 33 edges[numE].isBrg = 0; 34 edges[numE].more = isDup;//是否重边 35 head[u] = numE++; 36 37 //反向边,序号仅差1 38 edges[numE].v = u; 39 edges[numE].next = head[v]; 40 edges[numE].isBrg = 0; 41 edges[numE].more = isDup; 42 head[v] =numE++; 43 } 44 45 void bcc(int u, int p, bool isDup){ 46 low[u] = dfn[u] = ++clock; 47 S[topS++] = u; 48 inStack[u] = 1; 49 for(int i=head[u]; i != -1; i = edges[i].next){ 50 int v = edges[i].v; 51 if(v == p && (!isDup)) continue;//parent and no duplicate 52 if(!dfn[v]){//tree 53 bcc(v, u, edges[i].more); 54 low[u] = min(low[u], low[v]); 55 if(low[v] > dfn[u]){//bridge 56 numBrg++; 57 edges[i].isBrg = 1; 58 edges[i^1].isBrg = 1; 59 } 60 }else if(inStack[v])//backward or parent 61 low[u] = min(low[u], dfn[v]);//include parent 62 } 63 if(low[u] == dfn[u]){//new bcc 64 block++; 65 int t; 66 do{ 67 t = S[--topS]; 68 inStack[t] = 0; 69 belong[t] = block; 70 }while(t != u); 71 } 72 } 73 74 void init(){ 75 numE = 0; 76 numBrg = 0; 77 CLEAR(head, -1); 78 CLEAR(low, 0); CLEAR(dfn, 0); 79 CLEAR(inStack, 0); 80 CLEAR(belong, 0); 81 clock = block = topS = 0; 82 } 83 84 vector<int> G[MAX_N];//缩点后的新图 85 int dep[MAX_N]; 86 87 void dfs(int u){ 88 for(int i=0; i<G[u].size(); i++){ 89 int v = G[u][i]; 90 if(dep[v] == -1){ 91 dep[v] = dep[u]+1; 92 dfs(v); 93 } 94 } 95 } 96 97 int n, m; 98 struct Node 99 { 100 int u, v; 101 }nodes[MAX_M]; 102 bool cmp(Node a, Node b){ 103 if(a.u == b.u) return a.v < b.v; 104 return a.u < b.u; 105 } 106 bool same(Node a, Node b){ 107 if(a.u == b.u && a.v == b.v) return true; 108 return false; 109 } 110 111 int main() 112 { 113 FREAD("4612.txt"); 114 while(~scanf("%d%d", &n, &m) && n && m){ 115 init(); 116 for(int i=0; i<m; i++){ 117 int u, v; 118 scanf("%d%d", &u, &v); 119 if(u > v) swap(u, v); 120 nodes[i].u = u; nodes[i].v = v; 121 } 122 sort(nodes, nodes+m, cmp);//将重边聚到一起 123 int cur = 0, i = 1; 124 int flagDup = 0; 125 while(cur < m){ 126 while(i<m && same(nodes[cur], nodes[i])){ 127 flagDup = 1;//与cur重 128 i++; 129 } 130 if(flagDup) addEdge(nodes[cur].u, nodes[cur].v, 1); 131 else addEdge(nodes[cur].u, nodes[cur].v, 0); 132 //printf("add edge %d %d\n", nodes[cur].u, nodes[cur].v); 133 flagDup = 0; 134 cur = i++; 135 } 136 137 bcc(1, 0, 0);//0->1虚拟边 138 REPE(block) G[i].clear();//block个节点的图 139 REPE(n){//以block为编号,[1,block]建新图 140 for(int j=head[i]; j!=-1; j=edges[j].next){ 141 if(edges[j].isBrg){ 142 int u = i; 143 int v = edges[j].v; 144 G[belong[u]].pb(belong[v]);//缩点 145 } 146 } 147 } 148 CLEAR(dep, -1); 149 dep[1] = 0;//以1为根 150 dfs(1); 151 int deepest = 1, maxDep = -1; 152 REPE(block){//找到最深的 153 if(dep[i] > dep[deepest]){ 154 maxDep = dep[deepest]; 155 deepest = i; 156 } 157 } 158 CLEAR(dep, -1); 159 dep[deepest] = 0;//以deepest为根 160 dfs(deepest); 161 int len = 0;//树的直径 162 REPE(block) len = max(len, dep[i]); 163 164 printf("%d\n", numBrg - len); 165 } 166 return 0; 167 }
这道题从MLE改到WA改到RE再改到TLE,最终弃掉自己的版本学习了bin神的。发现他的实现很真实地反映了思路,比如bcc时,增加父节点p和边属性isDup两个参数,这样就通过v!=p && (!isDup)把无重边的父子边过滤掉,剩下真的后向边和有重边的父子边。我在从思路到代码的转换上还需要多加练习。