/*
對於邊雙連通分支,求法更爲簡單。只需在求出所有的橋以後,把橋邊刪除,\
原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分支。橋不屬於任何
一個邊雙連通分支,其餘的邊和每個頂點都屬於且只屬於一個邊雙連通分支。
一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖?方法爲首先求出所有的橋,
然後刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮爲一個頂點,
再把橋邊加回來,最後的這個圖一定是一棵樹,邊連通度爲1。
統計出樹中度爲1的節點的個數,即爲葉節點的個數,記爲leaf。則至少在樹上添加(leaf+1)/2條邊,
就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。具體方法爲,首先把兩個最近公共祖先最遠
的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,
因爲一個形成的環一定是雙連通的。然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,
恰好是(leaf+1)/2次,把所有點收縮到了一起。
*/
/*
(1) n 爲頂點數, 標號從 1 開始
(2) c 爲原圖的鄰接表, g 爲 E_BCC 圖的鄰接表
(3) num[u] 表示原圖中的點 u 屬於新圖中的第 num[u] 個 E_BCC
(4) edge[] 存儲所有的橋
(5) 注意 pool[M] 要開得足夠大以容得下新舊兩個圖中所有的邊
(6) E_BCC 圖中去掉了自環 ( 顯然不存在多重邊 )
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 11115;
const int M = 2000005;
struct List {
int v, id;
List *next;
} pool[M], *c[N], *g[N], *pp;
//c 爲原圖的鄰接表, g 爲 E_BCC 圖的鄰接表
//注意 pool[M] 要開得足夠大以容得下新舊兩個圖中所有的邊
inline void add_edge(int u, int v, int id, List *c[])
{
pp->v = v;
pp->id = id;
pp->next = c[u];
c[u] = pp ++;
}
struct Edge {
int u, v;
} edge[M];
//edge[] 存儲所有的橋,u,v爲橋的兩個頂點
int n, m, label, tot, top;
int low[N], dfn[N], num[N], stack[N];
bool eflag[M];
//label時間戳,tot連通塊數
//dfn用來保存時間戳(次序)編號,low保存頂點i或i的子樹最早的次序編號
//num[u] 表示原圖中的點 u 屬於新圖中的第 num[u] 個 E_BCC
void E_BCC_VISIT(int u)
{
low[u] = dfn[u] = label ++;
stack[++ top] = u;
for(List *p = c[u]; p; p = p->next) {
int v = p->v;
if(eflag[p->id]) continue;
eflag[p->id] = true;
//if(dfn[v]) { low[u] <?= dfn[v]; continue; }
if(dfn[v]){
if(low[u] > dfn[v]) low[u] = dfn[v];
continue;
}
E_BCC_VISIT(v);
//low[u] <?= low[v];
if(low[u] > low[v]) low[u]=low[v];
if(low[v] > dfn[u]) {
edge[m].u = u;//第m條橋的兩個頂點u,v
edge[m ++].v = v;
++ tot;
do {
num[stack[top]] = tot;
} while( stack[top --] != v );
}
}
}
void E_BCC()
{
int i;
tot = 0;
m = 0;/////
for(i = 1; i <= n; ++ i) dfn[i] = 0, num[i] = -1;
for(i = 0; i < m; ++ i) eflag[i] = false;
for(i = 1; i <= n; ++ i)
if(dfn[i] == 0) {
label = 1;
top = -1;
E_BCC_VISIT(i);
++ tot;
while( top >= 0 ) {
num[stack[top]] = tot;
-- top;
}
}
for(i = 1; i <= tot; ++ i) g[i] = NULL;
//for(i = 1; i <= n; ++ i) {//縮點,這題用不着
// int u = num[i];//u爲一個雙連通分量
//for(List *p = c[i]; p; p = p->next) {
// int v = num[p->v];//v是另一個雙連通分量
//if(u != v) add_edge(u, v, 0, g);//在兩個分量間建一條邊
//}
//}
}
int main()
{
int i, j, k;
while( scanf("%d %d", &n, &m) == 2 ) {
for(i = 1; i <= n; ++ i) c[i] = NULL;
pp = pool;
for(k = 0; k < m; ++ k) {
scanf("%d %d", &i, &j);
add_edge(i, j, k, c);
add_edge(j, i, k, c);
}
E_BCC();
if(m == 0){cout<<0<<endl; continue;}
int du[N]={0};
for(int i=0;i<m;i++){//橋即爲聯通塊的之間的邊,這裏處理僞縮點
//cout<<num[edge[i].u]<<' '<<num[edge[i].v]<<endl;
du[num[edge[i].u]]++;//要用num[]映射到連通塊編號上計算聯通塊的度
du[num[edge[i].v]]++;
}
int leaf=0;//樹葉
//cout<<tot<<endl<<m<<endl;
for(int i=1;i<=tot;i++) if(du[i]==1)leaf++;
cout<<(leaf+1)/2<<endl;
//for(int i=0;i<=m;i++)printf("num[%d]:%d\n",i,num[i]);
//printf("tot:%d m:%d\n",tot,m);
}
return 0;
}
poj3352Road Construction 邊雙連通+僞縮點
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.