1. 程式人生 > >連通分量專題

連通分量專題

dash ase cc++ int logs spa ring esp 一個

[連通分量專題]

硬著頭皮刷了下連通分量。。

強連通就不說了,是最基礎的部分;

割點(割頂),就是在無向圖中,刪掉這個點,使圖不連通的點(或者說使得原圖連通塊數量增加)。

割邊(橋),就是在無向圖中,刪掉這個邊,使圖不連通的邊(或者說使得原圖連通塊數量增加)。

那麽割頂和橋的求法很類似——

我們都采用DFS樹的方法,也記錄low,dfn等值,其中dfn表示訪問的次序,和求強連通分量的算法類似,low表示當前節點及其後代能連會的最早祖先的dfn值。

然後對於當前結點,枚舉與其相鄰的每條邊,這條邊可能是樹邊,反向邊,前向邊(返祖邊),橫向邊(橫叉邊)。

而現在,反向邊我們不考慮,橫向邊在過程中相當於不存在,只存在樹邊和前向邊。

如果是樹邊,則,low[u]=min(low[u],low[v]);

否則,low[u]=min(low[u],dfn[v])。怎麽樣,是不是和scc很像?

那怎麽判斷一個點是不是割頂?dfn[u]<=low[v];是不是橋?dfn[u]<low[v],畫個圖就好理解了。

那麽,我們再來探討雙連通。這是困擾我已久的東西。

雙連通又分為點雙和邊雙。

點雙相當於無向圖中的環,一個點雙分量中任意兩點至少存在兩條“點不重復”的路徑,點雙與點雙之間最多就1個公共點,這個點必然是割頂;

邊雙是什麽?一個邊雙分量中任意兩點至少存在兩條“邊不重復”的路徑,同理,橋不屬於任何邊雙,邊雙與邊雙之間由橋連接。

從而,我們可以通過找出割頂後找出點雙,找出橋後找邊雙,這是非常合乎情理的。(這就是為什麽一道點雙的題目,有人寫是割頂,有人寫是點雙,是我太naive了)

來幾個例題:

POJ - 3177

題目意思就是求出至少加幾條邊才能使原圖成為一個邊雙連通分量。

我們先把原圖縮點(無向圖縮點,註意反向邊),然後必然會形成一棵樹。

怎麽使這棵樹成為邊雙連通分量?顯然,我們一定是在葉子節點建邊能得到較優的解,那到底怎麽建邊?

每次找lca最遠的兩個點(一定是葉子節點啦)連起來,相當於他們路徑上所有點都縮到了一起,

最終要建(設子節點有c個)(c+1)/2條邊。

code:

技術分享
 1
#include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<stack> 5 using namespace std; 6 const int N=5005,M=20005; 7 int n,m,tot; 8 int lnk[N],nxt[M],son[M]; 9 int dfn[N],low[N],cloc; 10 stack <int> st; 11 bool inst[N]; 12 int scc,bel[N],degrs[N]; 13 void add(int x,int y) { 14 nxt[++tot]=lnk[x],son[tot]=y,lnk[x]=tot; 15 } 16 bool jug(int x,int y){ 17 if ((x&1)&&y==x+1) return 1; 18 if (!(x&1)&&y==x-1) return 1; 19 return 0; 20 } 21 void tarjan(int x,int fa) { 22 dfn[x]=low[x]=++cloc,inst[x]=1,st.push(x); 23 for (int j=lnk[x]; j; j=nxt[j]) 24 if (!jug(j,fa)) { 25 if (!dfn[son[j]]) { 26 tarjan(son[j],j); 27 low[x]=min(low[x],low[son[j]]); 28 } 29 else if (inst[son[j]]) { 30 low[x]=min(low[x],dfn[son[j]]); 31 } 32 } 33 if (low[x]==dfn[x]) { 34 scc++; int y; 35 do { 36 y=st.top(),st.pop(),bel[y]=scc,inst[y]=0; 37 }while (y!=x); 38 } 39 } 40 int main() { 41 scanf("%d%d",&n,&m),tot=0,scc=0; 42 for (int i=1; i<=m; i++) { 43 int x,y; scanf("%d%d",&x,&y); 44 add(x,y),add(y,x); 45 } 46 for (int i=1; i<=n; i++) if (!dfn[i]) tarjan(i,-1); 47 for (int i=1; i<=n; i++) 48 for (int j=lnk[i]; j; j=nxt[j]) 49 if (bel[i]!=bel[son[j]]) degrs[bel[son[j]]]++; 50 int ans=0; 51 for (int i=1; i<=scc; i++) if (degrs[i]==1) ans++; 52 printf("%d",(ans+1)/2); 53 return 0; 54 }
View Code

POJ -1236

簡單縮點,然後統計出入度分別為0的點。

第一問的答案就是入度為0的點的個數,第二問稍微復雜一點。

設入,出度為0的點分別有c1,c2個,則答案為max(c1,c2)個。

怎麽做?就是一個源點接在一個匯點上,這個匯點又接在另一個源點上。如果有多余的,那也要接在匯點(源點)上。

code:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<stack>
 5 using namespace std;
 6 const int N=105;
 7 int tot,lnk[N],nxt[N*N],son[N*N];
 8 int dfn[N],low[N],bel[N];
 9 int din[N],dout[N];
10 int n,scc,cloc;
11 bool inst[N];
12 stack <int> st;
13 void add(int x,int y) {
14     nxt[++tot]=lnk[x],son[tot]=y,lnk[x]=tot;
15 }
16 void tarjan(int x) {
17     low[x]=dfn[x]=++cloc,inst[x]=1,st.push(x);
18     for (int j=lnk[x]; j; j=nxt[j])
19         if (!dfn[son[j]]) {
20             tarjan(son[j]),low[x]=min(low[x],low[son[j]]);
21         } else if (inst[son[j]])
22             low[x]=min(low[x],dfn[son[j]]);
23     if (low[x]==dfn[x]) {
24         scc++;
25         for (int y=st.top(); ; y=st.top()) {
26             bel[y]=scc,inst[y]=0,st.pop();
27             if (y==x) return;
28         }
29     }
30 }
31 int main() {
32     scanf("%d",&n);
33     for (int i=1; i<=n; i++) {
34         int j; scanf("%d",&j);
35         for (; j; scanf("%d",&j)) add(i,j);
36     }
37     for (int i=1; i<=n; i++)
38         if (!dfn[i]) tarjan(i);
39     if (scc==1) {puts("1"),puts("0"); return 0;}
40     for (int i=1; i<=n; i++)
41         for (int j=lnk[i]; j; j=nxt[j])
42         if (bel[i]!=bel[son[j]])
43             dout[bel[i]]++,din[bel[son[j]]]++;
44     int cnt1=0,cnt2=0;
45     for (int i=1; i<=scc; i++) cnt1+=(din[i]==0);
46     for (int i=1; i<=scc; i++) cnt2+=(dout[i]==0);
47     printf("%d\n%d",cnt1,max(cnt1,cnt2));
48     return 0;
49 }
View Code

POJ - 3694

題意就是給你初始的一張圖,然後不斷建邊,每次建完問你目前橋的數量。

顯然,不能建一條tarjan一次,實在太慢。但我們肯定要預先知道哪些原邊是橋,有幾條。

那我們要做一次tarjan,會形成一棵DFS樹。那麽,如果要建一條(x,y)的邊,則相當於

x->lca(x,y)和y->lca(x,y)的邊都變成了不是橋的邊。有了這個小優化我們就能跑過去了。

code:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define Ms(a,x) memset(a,x,sizeof a)
 5 using namespace std;
 6 const int N=100005,M=500005;
 7 int tot,lnk[N],nxt[M],son[M];
 8 int dfn[N],low[N],fa[N];
 9 int n,m,cloc,b;
10 bool isb[M],vis[M];
11 inline int read() {
12     int x=0; char ch=getchar();
13     while (ch<0||ch>9) ch=getchar();
14     while (ch>=0&&ch<=9)
15         x=(x<<3)+(x<<1)+ch-0,ch=getchar();
16     return x;
17 }
18 void add(int x,int y) {
19     nxt[++tot]=lnk[x],son[tot]=y,lnk[x]=tot;
20 }
21 void tarjan(int x,int ff) {
22     low[x]=dfn[x]=++cloc,fa[x]=ff;
23     for (int j=lnk[x]; j; j=nxt[j]) {
24         int y=son[j];
25         if (!dfn[y]) {
26             vis[j]=vis[j^1]=1,tarjan(y,x);
27             low[x]=min(low[x],low[y]);
28             if (dfn[x]<low[y]) isb[y]=1,b++;
29         }else if (dfn[y]<dfn[x]&&!vis[j]) {
30             vis[j]=vis[j^1]=1;
31             low[x]=min(low[x],dfn[son[j]]);
32         }
33     }
34 }
35 int calc(int x,int y) {
36     if (dfn[x]<dfn[y]) swap(x,y);
37     while (dfn[x]>dfn[y]) {
38         if (isb[x]) b--,isb[x]=0;
39         x=fa[x];
40     }
41     while (x!=y) {
42         if (isb[y]) b--,isb[y]=0;
43         y=fa[y];
44     }
45     return b;
46 }
47 int main() {
48     int cas=0;
49     while (scanf("%d%d",&n,&m)&&(n+m>0)) {
50         if (cas) puts("");
51         printf("Case %d:\n",++cas),tot=1;
52         Ms(lnk,0),Ms(nxt,0);
53         for (int i=1; i<=m; i++) {
54             int x=read(),y=read();
55             add(x,y),add(y,x);
56         }
57         Ms(dfn,0),Ms(isb,0),Ms(fa,0);
58         cloc=b=0,tarjan(1,0);
59         for (int Q=read(); Q; Q--) {
60             int x=read(),y=read();
61             printf("%d\n",calc(x,y));
62         }
63     }
64     return 0;
65 }
View Code

POJ - 2942

未完成。

連通分量專題