菜鸟系列——双连通分量
2015-09-09 19:48
316 查看
菜鸟就要老老实实重新学起:
双连通分量
就是无向图中的点集满足:任意两点之间能够有不止一条通路。边双连通分量
就是两点间不含有桥的点集,桥就是满足去除该边之后图不再连通的边,也就是任意两点间有不同的边集可以到达,边连通分量之间可能有桥连接。求解就是直接用求有向图强连通分量的tarjan算法,因为是无向图,深搜时上不能返回父节点就行了。
模版:
#define N 110 vector<int>g ; stack<int>st; // 深度优先搜索访问次序, 能追溯到的最早的次序 int dfn ,low ; // 检查是否在栈中, 记录每个点在第几号强连通分量里 int inStack ,belong ; // 索引号,双连通分量个数 int index,cnt; int n,m; void init() { for(int i=0;i<N;i++) g[i].clear(); while(!st.empty())st.pop(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(inStack, 0, sizeof(inStack)); index = cnt = 1; } void tarjan(int x,int fa) { int i; // 刚搜到一个节点时low = dfn low[x] = dfn[x] = index; index++; st.push(x); inStack[x] = 1; int len = g[x].size(); int mark = 0; for(i=0;i<len;i++) { int t=g[x][i]; //无向图双连通分量,mark防重边。 if(!mark && t==fa) { mark=1; continue; } if(!dfn[t]) { tarjan(t,x); // 回溯的时候改变当前节点的low值 low[x] = min(low[x], low[t]); } // 如果新搜索到的节点已经被搜索过而且现在在栈中 else if(inStack[t]) { // 更新当前节点的low值,这里的意思是两个节点之间有一条可达边, // 而前面节点已经在栈中,那么后面的节点就可能和前面的节点在一个联通分量中 low[x] = min(low[x], dfn[t]); } } // 最终退回来的时候 low == dfn , 没有节点能将根节点更新,那必然就是根节点 if(low[x] == dfn[x]) { int temp; // 一直出栈到此节点, 这些元素是一个双联通分量 while(!st.empty()) { temp = st.top(); st.pop(); belong[temp] = cnt; // 标记双联通分量 inStack[temp] = 0; if(temp == x) break; } cnt++; } } int solve() { for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i,i); return cnt; }
点双连通分量
就是内部不含有割点的点集,割点就是满足去除该点之后图不联通点,也就是任意两点间有不同的点集可以到达,两个点联通分量之间可能有割点连接,割点属于两个联通分量。求解也是通过tarjan深搜,但栈中存放边,将图分成不同的点联通分量也就是块。
模版:
#define N 112345 struct node { int x,y; node(int a=0, int b=0) { x=a;y=b; } }tn; //blocks[]存每个块包含的点,bridge存是桥的边 vector<int>g ,blocks ; vector<node>bridge; stack<node>st; // 深度优先搜索访问次序, 能追溯到的最早的次序 int dfn ,low ; bool vis ; // 索引号,块的个数 int index,cnt; int n,m; void init() { for(int i=0;i<N;i++) g[i].clear(),blocks[i].clear(); bridge.clear(); while(!st.empty())st.pop(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); index = cnt = 1; } void judge(int u,int v) { int x,y; node temp; memset(vis,false,sizeof(vis)); while(!st.empty()) { temp = st.top();st.pop(); x=temp.x;y=temp.y; if(!vis[y])blocks[cnt].push_back(y),vis[y]=true; //一直到最后一条树枝边为止,起点并不属于这个双连通分量,如果属于,已在后向边中加入。 if(x==u) break; if(!vis[x])blocks[cnt].push_back(x),vis[x]=true; } cnt++; } void tarjan(int x,int fa) { low[x] = dfn[x] = index++; int len = g[x].size(); for(int i=0;i<len;i++) { int t=g[x][i]; if(t==fa) continue; if(!dfn[t] && dfn[t]<dfn[x]) { //加入树枝边 st.push(node(x,t)); tarjan(t,x); low[x] = min(low[x], low[t]); if(dfn[x]<=low[t]) judge(x,t); if(dfn[x]<low[t]) bridge.push_back(node(x,t)); } else if(dfn[t] < dfn[x]) { //加入后向边 st.push(node(x,t)); low[x] = min(low[x], dfn[t]); } } } int solve() { for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i,i); return cnt; }
eg:
POJ3352 Road Construction
http://poj.org/problem?id=3352题意:
给出各景点之间的路线,要求在修任意一条路时还能够到达所有景点,求要至少多修多少临时的桥。思路:
就是求加入最少的边使得整个图变成双连通图,只要求出所有双连通分量然后缩点连线,使之成为双连通图就行了,求双连通分量缩点用tarjan算法,之后对于所有的度数为一的点连线即可。答案就是(度数唯一的点+1)/2;
code:
#define N 112345 int n,m; int flag,sum,ave,ans,res,len,ans1,ans2; int a ,b ; vector<int>g ; stack<int>st; int dfn ,low ; int inStack ,belong ; int index,cnt; void init() { for(int i=0;i<N;i++) g[i].clear(); while(!st.empty())st.pop(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(inStack, 0, sizeof(inStack)); memset(a,0,sizeof(a)); index = cnt = 1; } void tarjan(int x, int fa) { int i, a; low[x] = dfn[x] = index; index++; st.push(x); inStack[x] = 1; int len = g[x].size(); for(i=0;i<len;i++) { int t=g[x][i]; if(t == fa) continue; if(!dfn[t]) { tarjan(t,x); low[x] = min(low[x], low[t]); } else if(inStack[t]) low[x] = min(low[x], dfn[t]); } if(low[x] == dfn[x]) { int temp; while(!st.empty()) { temp = st.top(); st.pop(); belong[temp] = cnt; inStack[temp] = 0; if(temp == x) break; } cnt++; } } int solve() { for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i,i); return cnt; } int main() { int i,j,k,kk,t,x,y,z; while(scanf("%d%d",&n,&m)!=EOF&&n) { init(); for(i=0;i<m;i++) { scanf("%d%d",&x,&y); g[x].push_back(y); g[y].push_back(x); } solve(); for(i=1;i<=n;i++) for(j=0;j<g[i].size();j++) if(belong[i]!=belong[g[i][j]]) a[belong[i]]++,a[belong[g[i][j]]]++; sum=0; for(i=1;i<cnt;i++) if(a[i]==2) sum++; sum++; printf("%d\n",sum/2); } return 0; }
POJ3177 Redundant Pathsac
http://poj.org/problem?id=3177题意:
n个农场,要求彼此之间至少有两条路线,求要新建多少路。思路:
同上题完全一样,就是这题会有重边,注意加flag标记下。code:
#define N 112345 int n,m; int flag,sum,ave,ans,res,len,ans1,ans2; int a ,b ; vector<int>g ; stack<int>st; int dfn ,low ; int inStack ,belong ; int index,cnt; void init() { for(int i=0;i<N;i++) g[i].clear(); while(!st.empty())st.pop(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(inStack, 0, sizeof(inStack)); memset(a,0,sizeof(a)); index = cnt = 1; } void tarjan(int x, int fa) { int i, a; low[x] = dfn[x] = index; index++; st.push(x); inStack[x] = 1; int len = g[x].size(); flag=0; for(i=0;i<len;i++) { int t=g[x][i]; if(t == fa && !flag) { flag=1; continue; } if(!dfn[t]) { tarjan(t,x); low[x] = min(low[x], low[t]); } else if(inStack[t]) low[x] = min(low[x], dfn[t]); } if(low[x] == dfn[x]) { int temp; while(!st.empty()) { temp = st.top(); st.pop(); belong[temp] = cnt; inStack[temp] = 0; if(temp == x) break; } cnt++; } } int solve() { for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i,i); return cnt; } int main() { int i,j,k,kk,t,x,y,z; while(scanf("%d%d",&n,&m)!=EOF&&n) { init(); for(i=0;i<m;i++) { scanf("%d%d",&x,&y); g[x].push_back(y); g[y].push_back(x); } solve(); for(i=1;i<=n;i++) for(j=0;j<g[i].size();j++) if(belong[i]!=belong[g[i][j]]) a[belong[i]]++,a[belong[g[i][j]]]++; sum=0; for(i=1;i<cnt;i++) if(a[i]==2) sum++; sum++; printf("%d\n",sum/2); } return 0; }