您的位置:首页 > 其它

菜鸟系列——双连通分量

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: