菜鸟系列——双连通分量

来源:互联网 发布:sam软件和au 编辑:程序博客网 时间:2024/05/16 14:51

菜鸟就要老老实实重新学起:

双连通分量

就是无向图中的点集满足:任意两点之间能够有不止一条通路。

边双连通分量

就是两点间不含有桥的点集,桥就是满足去除该边之后图不再连通的边,也就是任意两点间有不同的边集可以到达,边连通分量之间可能有桥连接。

求解就是直接用求有向图强连通分量的tarjan算法,因为是无向图,深搜时上不能返回父节点就行了。

模版:

#define N 110vector<int>g[N];stack<int>st;// 深度优先搜索访问次序, 能追溯到的最早的次序int dfn[N],low[N];// 检查是否在栈中, 记录每个点在第几号强连通分量里int inStack[N],belong[N];// 索引号,双连通分量个数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 = dfnlow[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 112345struct node{    int x,y;    node(int a=0, int b=0)    {        x=a;y=b;    }}tn;//blocks[]存每个块包含的点,bridge存是桥的边vector<int>g[N],blocks[N];vector<node>bridge;stack<node>st;// 深度优先搜索访问次序, 能追溯到的最早的次序int dfn[N],low[N];bool vis[N];// 索引号,块的个数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 112345int n,m;int flag,sum,ave,ans,res,len,ans1,ans2;int a[N],b[N];vector<int>g[N];stack<int>st;int dfn[N],low[N];int inStack[N],belong[N];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 112345int n,m;int flag,sum,ave,ans,res,len,ans1,ans2;int a[N],b[N];vector<int>g[N];stack<int>st;int dfn[N],low[N];int inStack[N],belong[N];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;}

0 0