連通分量專題
[連通分量專題]
硬著頭皮刷了下連通分量。。
強連通就不說了,是最基礎的部分;
割點(割頂),就是在無向圖中,刪掉這個點,使圖不連通的點(或者說使得原圖連通塊數量增加)。
割邊(橋),就是在無向圖中,刪掉這個邊,使圖不連通的邊(或者說使得原圖連通塊數量增加)。
那麽割頂和橋的求法很類似——
我們都采用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:
1View Code#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 }
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
未完成。
連通分量專題