POJ 3694 Network ★(边双连通分量+并查集缩点+LCA)
生活随笔
收集整理的這篇文章主要介紹了
POJ 3694 Network ★(边双连通分量+并查集缩点+LCA)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
[題意]一個無向圖可以有重邊,下面q個操作,每次在兩個點間連接一條有向邊,每次連接后整個無向圖還剩下多少橋(每次回答是在上一次連邊的基礎之上) [分析]好題,做完后漲了很多姿勢~ 普通做法當然就是每加一條邊重新算一次橋,但這樣復雜度將達到O(q*M),顯然要超時。。所以我們需要“動態地”在原圖的基礎上求橋~ 我們可以先把圖求一次邊雙連通分量(BCC)然后縮點,因為同一雙連通分量中沒有橋,加邊沒有影響。一個很重要的性質就是{一個圖求一次邊雙連通分量縮點后將變成一顆樹或者森林,并且樹中的每條邊都是橋}。因為此題中說所有的點都有邊連著,所以這里縮點后是一棵樹。 顯然,樹中每添加一條邊,就會形成一個環,而這個環中的邊將不再是橋,并且他們構成新的邊雙連通分量,所以我們每次在橋中減去這些邊,并把他們縮成一個點。 第一,怎么求在樹中加邊(u,v)后形成的環? 我們可以求出u,v的LCA,然后環就是v->LCA(u,v)->u>(u,v)->v. 第二,怎么縮點? 以前一直做的是“靜態縮點”,就是求一遍邊BCC后圖的結構就不變了,此時我們可以在求出每個點所屬的BCC(bcc[i])后以BCC的標號來代替縮后的點。如果我們動態地加邊,則每次都要修改原BCC中所有的點成新BCC,所以直接這樣改行不通。這里我們是不是發現它很像……并查集?對!就是用并查集維護bcc[]!這是我做這道題學到的最重要的姿勢:用并查集維護動態加邊的縮點。 當然此題中我們沒有用到bcc[],因為在求LCA時我們用的樸素的方法:{用level[]表示每個節點的深度,兩個點同時向根爬,深度深的節點先爬(LCA(u,v) = LCA(u, father[v]) ),深度相同時兩個點一起爬(LCA(u, v) = LCA(father[u], father[v]) ),直到兩個點相同。}這種方法的好處是可以一邊求LCA一邊縮點。那么我們就需要一個father[]來維護節點的父節點。這樣我們就不需要bcc[]了,直接用并查集維護father[],并用它表示縮點及所屬BCC。 ?
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MID(x,y) ((x+y)/2)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;const int MAXV = 100005;
const int MAXE = 400005;
struct node{int u, v;int next;int opp; //把一個無向邊拆成兩個有向邊,對應反向邊標號
}arc[MAXE];
int cnt, head[MAXV];
void init(){cnt = 0;mem(head, -1);return ;
}
void add(int u, int v){arc[cnt].u = u;arc[cnt].v = v;arc[cnt].next = head[u];arc[cnt].opp = cnt + 1;head[u] = cnt ++;arc[cnt].u = v;arc[cnt].v = u;arc[cnt].next = head[v];arc[cnt].opp = cnt - 1;head[v] = cnt ++;return ;
}
int id, dfn[MAXV], low[MAXV];
int level[MAXV];
int father[MAXV]; //點的父節點,便于爬山坡(向根節點走),也用于并查集縮點
int bridge_num, bridge[MAXV]; //標記該邊是不是橋,這里用橋的子節點表示
bool vis_arc[MAXE]; //一條邊無向邊(兩個有向邊)只訪問一次,
int find(int u){ //并查集 + 路徑壓縮處理縮點, 縮點合并邊時把子節點的父親設為父節點if(father[u] != u)return father[u] = find(father[u]);elsereturn u;
}
void tarjan(int u, int l){dfn[u] = low[u] = ++id;level[u] = l;for (int i = head[u]; i != -1; i = arc[i].next){int v = arc[i].v;if (vis_arc[i]) continue;vis_arc[i] = vis_arc[arc[i].opp] = 1;if (!dfn[v]){father[v] = u;tarjan(v, l+1);low[u] = min(low[u], low[v]);}else{low[u] = min(low[u], dfn[v]);}if (dfn[u] < low[v]){bridge[v] = 1;bridge_num ++;}else{int x = find(u);int y = find(v);if (x != y)father[y] = x;}}
}
void solve(int n){id = bridge_num = 0;mem(dfn, 0);mem(low, 0);mem(level, 0);mem(bridge, 0);mem(vis_arc, 0);for (int i = 0; i <= n; i ++)father[i] = i;for (int i = 1; i <= n; i ++){if (!dfn[i])tarjan(1, 1);}return ;
}
vector path;
int lca(int u, int v){path.clear();if (level[u] > level[v]) swap(u, v);while(u != v){while(level[u] != level[v]){if(level[v] > level[u]){if (bridge[v]){bridge[v] = 0;bridge_num --;}path.push_back(v);v = father[v];}else{if (bridge[u]){bridge[u] = 0;bridge_num --;}path.push_back(u);u = father[u];}}while(u != v){if (bridge[v]){bridge[v] = 0;bridge_num --;}if (bridge[u]){bridge[u] = 0;bridge_num --;}path.push_back(u);path.push_back(v);v = father[v];u = father[u];}}int lc = u;//把加邊后的環用并查集縮成一個點for (int i = 0; i < (int)path.size(); i ++){int u = path[i];father[u] = lc;}return bridge_num;
}
int main(){int n, m, t = 1;while(scanf("%d %d", &n, &m) != EOF){if (n + m == 0)break;init();for (int i = 0; i < m; i ++){int u, v;scanf("%d %d", &u, &v);add(u, v);}solve(n);int q;scanf("%d", &q);printf("Case %d:\n", t);for (int i = 0; i < q; i ++){int u, v;scanf("%d %d", &u, &v);printf("%d\n", lca(u, v));}puts("");t ++;}return 0;
}
轉載于:https://www.cnblogs.com/AbandonZHANG/archive/2013/06/15/4114250.html
總結
以上是生活随笔為你收集整理的POJ 3694 Network ★(边双连通分量+并查集缩点+LCA)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试和人生目标(转)
- 下一篇: 批量scp脚本——从多台机器拷贝文件