连通分量专题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连通分量专题相关的知识,希望对你有一定的参考价值。

[连通分量专题]

硬着头皮刷了下连通分量。。

 

强连通就不说了,是最基础的部分;

割点(割顶),就是在无向图中,删掉这个点,使图不连通的点(或者说使得原图连通块数量增加)。

割边(桥),就是在无向图中,删掉这个边,使图不连通的边(或者说使得原图连通块数量增加)。

那么割顶和桥的求法很类似——

我们都采用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

未完成。

以上是关于连通分量专题的主要内容,如果未能解决你的问题,请参考以下文章

连通图(Tarjan算法) 专题总结

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

强连通分量(tarjan求强连通分量)

HDU 1269 迷宫城堡 tarjan算法求强连通分量

强连通分量——tarjan算法

Kosaraju算法——强连通分量