【POJ - 3694】Network(对dfn求lca 或 缩点+lca 或 边双连通+并查集)
題干:
網(wǎng)絡(luò)管理員管理大型網(wǎng)絡(luò)。該網(wǎng)絡(luò)由N臺計算機和成對計算機之間的M鏈路組成。任何一對計算機都通過連續(xù)的鏈接直接或間接連接,因此可以在任何兩臺計算機之間轉(zhuǎn)換數(shù)據(jù)。管理員發(fā)現(xiàn)某些鏈接對網(wǎng)絡(luò)至關(guān)重要,因為任何一個鏈接的故障都可能導(dǎo)致某些計算機之間無法轉(zhuǎn)換數(shù)據(jù)。他把這種聯(lián)系稱為橋梁。他計劃逐一添加一些新鏈接以消除所有橋梁。 您將通過在添加每個新鏈接后報告網(wǎng)絡(luò)中的網(wǎng)橋數(shù)來幫助管理員。
輸入
輸入包含多個測試用例。每個測試用例以包含兩個整數(shù)N(1≤N≤100,000)和M(N-1≤M≤200,000)的行開始。 以下M行中的每一行包含兩個整數(shù)A和B(1≤A≠B≤N),表示計算機A和B之間的鏈接。計算機編號從1到N.保證任何兩臺計算機都連接在一起最初的網(wǎng)絡(luò)。 下一行包含一個整數(shù)Q(1≤Q≤1,000),這是管理員計劃逐個添加到網(wǎng)絡(luò)的新鏈接數(shù)。 以下Q行的第i行包含兩個整數(shù)A和B(1≤A≠B≤N),這是連接計算機A和B的第i個新鏈接。 最后一個測試用例后跟一行包含兩個零的行。
輸出
對于每個測試用例,打印一行包含測試用例編號(以1開頭)和Q行,其中第i行包含一個整數(shù),表示添加第一個i新鏈接后網(wǎng)絡(luò)中的網(wǎng)橋數(shù)。在每個測試用例的輸出后打印一個空行。
Sample Input
3 2 1 2 2 3 2 1 2 1 3 4 4 1 2 2 1 2 3 1 4 2 1 2 3 4 0 0Sample Output
Case 1: 1 0Case 2: 2 0題目大意:
? ?給了一個連通圖。 問加入邊的過程中,橋的個數(shù)。
解題報告:
? 在線維護橋的個數(shù)就可以了。注意縮點的使用。
AC代碼1:(對dfn求lca+暴力)(1157ms)
#include<cstdio> #include<iostream> #include<algorithm> #include<queue> #include<map> #include<vector> #include<set> #include<string> #include<cmath> #include<cstring> #define F first #define S second #define ll long long #define pb push_back #define pm make_pair using namespace std; typedef pair<int,int> PII; const int MAX = 2e5 + 5; struct Edge {int u,v;int ne; } e[MAX<<1]; int dfn[MAX],low[MAX],clk; int head[MAX],tot,fa[MAX]; int qiao[MAX]; int n,m,ans; void init() {for(int i = 1; i<=n; i++) {dfn[i]=low[i]=qiao[i]=fa[i]=0;//如果不初始化fa???head[i] = -1;}tot = 0;clk = 0;ans = 0; } void add(int u,int v) {e[++tot].u = u;e[tot].v = v;e[tot].ne = head[u];head[u] = tot; } void tarjan(int x,int rt) {//注意這里fa和rt是不一樣的含義!!dfn[x] = low[x] = ++clk;fa[x] = rt;for(int i = head[x]; ~i; i = e[i].ne) {int v = e[i].v;if(v == rt) continue;if(dfn[v] == 0) {tarjan(v,x);low[x] = min(low[x],low[v]);if(low[v] > dfn[x]) {qiao[v] = 1;ans++;}} else low[x] = min(low[x],dfn[v]);} } void lca(int u, int v) {while(dfn[v] > dfn[u]) {if(qiao[v]) ans--;qiao[v] = 0;v = fa[v];}while(dfn[u] > dfn[v]) {if(qiao[u]) ans--;qiao[u] = 0;u = fa[u];}while(u != v) {if(qiao[u]) ans--;if(qiao[v]) ans--;qiao[u] = qiao[v] = 0;u = fa[u];v = fa[v];} } int main() {int a,b,iCase=0;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;init();for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b); add(b,a);}tarjan(1,0);int q;scanf("%d",&q);printf("Case %d:\n",++iCase);while(q--) {scanf("%d%d",&a,&b);lca(a,b);printf("%d\n", ans);}printf("\n");}return 0 ; }其實對上面這個代碼的lca函數(shù),做了很多無用的工作,因為u和v最后可能都會回到1頂點。
實測這樣寫也可以過:(969ms)
void lca(int u, int v) {if(dfn[u] > dfn[v]) swap(u,v); while(dfn[v] > dfn[u]) {if(qiao[v]) ans--;qiao[v] = 0;v = fa[v];}while(u != v) {if(qiao[u]) ans--;qiao[u] = 0;u = fa[u];} }AC代碼2:(縮點+lca)(2891ms)
#include<cstdio> #include<iostream> #include<algorithm> #include<queue> #include<map> #include<vector> #include<set> #include<string> #include<cmath> #include<cstring> #define F first #define S second #define ll long long #define pb push_back #define pm make_pair using namespace std; typedef pair<int,int> PII; const int MAX = 2e5 + 5; struct Edge {int u,v;int ne; } e[MAX<<1]; vector<int> vv[MAX]; int dep[MAX]; int dfn[MAX],low[MAX],stk[MAX],col[MAX],clk,index,bcc; int head[MAX],tot,fa[MAX]; int qiao[MAX],is[MAX];//qiao數(shù)組用來記錄原圖中的每一個點是否是橋的終點,is數(shù)組用來記錄構(gòu)造的那棵樹上的每一個新頂點編號,是否還沒被遍歷過。 也就是用一個點去代表這個邊雙連通分量 int n,m,ans; void init() {for(int i = 1; i<=n; i++) {dfn[i]=low[i]=qiao[i]=fa[i]=is[i]=col[i]=0;head[i] = -1;vv[i].clear();}tot = 0;clk = index = bcc = 0;ans = 0; } void add(int u,int v) {e[++tot].u = u;e[tot].v = v;e[tot].ne = head[u];head[u] = tot; } void tarjan(int x,int rt) {dfn[x] = low[x] = ++clk;stk[++index] = x;for(int i = head[x]; ~i; i = e[i].ne) {int v = e[i].v;if(v == rt) continue;if(dfn[v] == 0) {tarjan(v,x);low[x] = min(low[x],low[v]);if(low[v] > dfn[x]) {qiao[v] = 1;ans++;}} else low[x] = min(low[x],dfn[v]);}if(dfn[x] == low[x]) {bcc++;//理論上來說應(yīng)該等于ans+1 while(1) {int tmp = stk[index];index--;col[tmp] = bcc;if(tmp == x) break;}} } void bfs() {queue<int> q;q.push(1);//initfor(int i = 1; i<=n; i++) dep[i]=0,is[i]=0;is[1]=0;dep[1]=1;fa[1] = -1;//人為規(guī)定一個 while(!q.empty()) {int cur = q.front();q.pop();int up = vv[cur].size();for(int i = 0; i<up; i++) {int v = vv[cur][i];if(dep[v]) continue;dep[v] = dep[cur] + 1;fa[v] = cur;is[v]=1; q.push(v); }} } void lca(int u,int v) {if(dep[u] < dep[v]) swap(u,v);while(dep[u] > dep[v]) {if(is[u]) ans--,is[u]=0;u = fa[u];} if(u == v) return;while(u != v) {if(is[u]) ans--,is[u]=0;if(is[v]) ans--,is[v]=0;u = fa[u];v = fa[v];} } int main() {int a,b,iCase=0;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;init();for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b); add(b,a);}tarjan(1,0);for(int u = 1; u<=n; u++) {for(int i = head[u]; ~i; i = e[i].ne) {int v = e[i].v;if(qiao[v] == 0) continue;vv[col[u]].pb(col[v]);vv[col[v]].pb(col[u]);}}bfs();int q;scanf("%d",&q);printf("Case %d:\n",++iCase);while(q--) {scanf("%d%d",&a,&b);lca(col[a],col[b]);//每次暴力a的bcc 到 b的bcc這條路徑。 printf("%d\n", ans);}printf("\n");}return 0 ; }AC代碼3:(454ms)
因為對于無向圖的tarjan算法有一個性質(zhì),就是你只要搜素進去,那肯定就把這一個bcc全都搜完,然后再進入另一個。
換種方向考慮,因為是搜索,所以對于查詢的兩個點u和v,肯定有個他倆的共同起點(祖先),而由于搜索的特性,所以dfn[u]一直減小(通過讓u=fa[u]),當(dāng)dfn[u]<dfn[v]的時候,此時的u要么是v,要么是u和v的最近公共祖先。
對于很多跑的飛快(400ms左右)的代碼,多半是用并查集來寫的(把qiao數(shù)組換成了并查集來看,差不多這樣,然后改一下 tarjan函數(shù)的關(guān)鍵點部分 和 lca函數(shù)的部分),但是對于lca部分,也是暴力,那么為什么會快這么多呢?研究了半天,發(fā)現(xiàn)就是因為他在進入lca函數(shù)之前加了一句if(col[a] != col[b]) ,代碼如下:
#include<cstdio> #include<iostream> #include<algorithm> #include<queue> #include<map> #include<vector> #include<set> #include<string> #include<cmath> #include<cstring> #define F first #define S second #define ll long long #define pb push_back #define pm make_pair using namespace std; typedef pair<int,int> PII; const int MAX = 2e5 + 5; struct Edge {int u,v;int ne; } e[MAX<<1]; vector<int> vv[MAX]; int dep[MAX]; int dfn[MAX],low[MAX],stk[MAX],col[MAX],clk,index,bcc; int head[MAX],tot,fa[MAX]; int qiao[MAX],is[MAX];//qiao數(shù)組用來記錄原圖中的每一個點是否是橋的終點,is數(shù)組用來記錄構(gòu)造的那棵樹上的每一個新頂點編號,是否還沒被遍歷過。 int n,m,ans; void init() {for(int i = 1; i<=n; i++) {dfn[i]=low[i]=qiao[i]=fa[i]=is[i]=col[i]=0;head[i] = -1;vv[i].clear();}tot = 0;clk = index = bcc = 0;ans = 0; } void add(int u,int v) {e[++tot].u = u;e[tot].v = v;e[tot].ne = head[u];head[u] = tot; } void tarjan(int x,int rt) {dfn[x] = low[x] = ++clk;stk[++index] = x;fa[x] = rt;for(int i = head[x]; ~i; i = e[i].ne) {int v = e[i].v;if(v == rt) continue;if(dfn[v] == 0) {tarjan(v,x);low[x] = min(low[x],low[v]);if(low[v] > dfn[x]) {qiao[v] = 1;ans++;}} else low[x] = min(low[x],dfn[v]);}if(dfn[x] == low[x]) {bcc++;//理論上來說應(yīng)該等于ans+1 while(1) {int tmp = stk[index];index--;col[tmp] = bcc;if(tmp == x) break;}} } void lca(int u, int v) {if(dfn[u] > dfn[v]) swap(u,v); while(dfn[v] > dfn[u]) {if(qiao[v]) ans--;qiao[v] = 0;v = fa[v];}while(u != v) {if(qiao[u]) ans--;qiao[u] = 0;u = fa[u];} } int main() {int a,b,iCase=0;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;init();for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b); add(b,a);}tarjan(1,0);int q;scanf("%d",&q);printf("Case %d:\n",++iCase);while(q--) {scanf("%d%d",&a,&b);if(col[a] != col[b]) lca(a,b);//每次暴力a的bcc 到 b的bcc這條路徑。 printf("%d\n", ans);}printf("\n");}return 0 ; }思考:
對于AC代碼1中的那個改進,我們拿到AC代碼2的思路來行不行呢?
答案是不行的,因為你將圖轉(zhuǎn)化成了一棵樹,然后用bfs生成的dep數(shù)組去做lca,(lca做法也是:先把u設(shè)置成dep大的,然后讓u一直向上找,一直到dep[u] <=?dep[v]則第一個循環(huán)結(jié)束,然后再只動v,一直到u==v則第二個循環(huán)結(jié)束)那么得到的當(dāng)dep[u] <=?dep[v]了之后,不能確保:此時的u要么等于v,要么是u和v的最近公共祖先,(想想普通的倍增做的lca的圖呀,很容易找到反例)所以不能這樣做,不然會出現(xiàn)v==1了,但是u還在下面,這樣就死循環(huán)了,所以會TLE。
TLE代碼:
#include<cstdio> #include<iostream> #include<algorithm> #include<queue> #include<map> #include<vector> #include<set> #include<string> #include<cmath> #include<cstring> #define F first #define S second #define ll long long #define pb push_back #define pm make_pair using namespace std; typedef pair<int,int> PII; const int MAX = 2e5 + 5; struct Edge {int u,v;int ne; } e[MAX<<1]; vector<int> vv[MAX]; int dep[MAX]; int dfn[MAX],low[MAX],stk[MAX],col[MAX],clk,index,bcc; int head[MAX],tot,fa[MAX]; int qiao[MAX],is[MAX];//qiao數(shù)組用來記錄原圖中的每一個點是否是橋的終點,is數(shù)組用來記錄構(gòu)造的那棵樹上的每一個新頂點編號,是否還沒被遍歷過。 int n,m,ans; void init() {for(int i = 1; i<=n; i++) {dfn[i]=low[i]=qiao[i]=fa[i]=is[i]=col[i]=0;head[i] = -1;vv[i].clear();}tot = 0;clk = index = bcc = 0;ans = 0; } void add(int u,int v) {e[++tot].u = u;e[tot].v = v;e[tot].ne = head[u];head[u] = tot; } void tarjan(int x,int rt) {dfn[x] = low[x] = ++clk;stk[++index] = x;for(int i = head[x]; ~i; i = e[i].ne) {int v = e[i].v;if(v == rt) continue;if(dfn[v] == 0) {tarjan(v,x);low[x] = min(low[x],low[v]);if(low[v] > dfn[x]) {qiao[v] = 1;ans++;}} else low[x] = min(low[x],dfn[v]);}if(dfn[x] == low[x]) {bcc++;//理論上來說應(yīng)該等于ans+1 while(1) {int tmp = stk[index];index--;col[tmp] = bcc;if(tmp == x) break;}} } void bfs() {queue<int> q;q.push(1);//initfor(int i = 1; i<=n; i++) dep[i]=0,is[i]=0;is[1]=0;dep[1]=1;fa[1] = -1;//人為規(guī)定一個 while(!q.empty()) {int cur = q.front();q.pop();int up = vv[cur].size();for(int i = 0; i<up; i++) {int v = vv[cur][i];if(dep[v]) continue;dep[v] = dep[cur] + 1;fa[v] = cur;is[v]=1; q.push(v); }} } void lca(int u,int v) {if(dep[u] < dep[v]) swap(u,v);while(dep[u] > dep[v]) {if(is[u]) ans--,is[u]=0;u = fa[u];} if(u == v) return;while(u != v) {if(is[v]) ans--,is[v]=0;v = fa[v];} } int main() {int a,b,iCase=0;while(~scanf("%d%d",&n,&m)) {if(n == 0 && m == 0) break;init();for(int i = 1; i<=m; i++) {scanf("%d%d",&a,&b);add(a,b); add(b,a);}tarjan(1,0);for(int u = 1; u<=n; u++) {for(int i = head[u]; ~i; i = e[i].ne) {int v = e[i].v;if(qiao[v] == 0) continue;vv[col[u]].pb(col[v]);vv[col[v]].pb(col[u]);}}bfs();int q;scanf("%d",&q);printf("Case %d:\n",++iCase);while(q--) {scanf("%d%d",&a,&b);if(col[a] != col[b]) lca(col[a],col[b]);//每次暴力a的bcc 到 b的bcc這條路徑。 printf("%d\n", ans);}printf("\n");}return 0 ; }?
總結(jié)
以上是生活随笔為你收集整理的【POJ - 3694】Network(对dfn求lca 或 缩点+lca 或 边双连通+并查集)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高考成绩公布 毛坦厂复读生已排起长队!学
- 下一篇: 【牛客 - 330G】Applese 的