[连通分量专题]
硬着头皮刷了下连通分量。。
强连通就不说了,是最基础的部分;
割点(割顶),就是在无向图中,删掉这个点,使图不连通的点(或者说使得原图连通块数量增加)。
割边(桥),就是在无向图中,删掉这个边,使图不连通的边(或者说使得原图连通块数量增加)。
那么割顶和桥的求法很类似——
我们都采用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 }
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 }
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 }
POJ - 2942
未完成。