zoukankan      html  css  js  c++  java
  • 图论:双连通分支-点

    POJ2942:利用Tarjan求无向图的点双连通分支

    首先科普一下点双连通分支的求法:

    貌似看起来繁琐而复杂。。

    然而复杂的确实这道例题,暂时没有找到特别裸的。。

    题干大意是这样的,开会,然后给出了一张图,边所连接的两个点互相憎恶,开会的时候只能奇数个人一起圆桌开,问T几个人会世界和平

    做法的话,首先求原图的补图,然后求出补图的点双连通分量,对于每一个连通分量判断是否是二分图,如果不是二分图那么满足条件,如果是那就要T人了

    这里的重点不是二分图判定,而是求点双连通分量的部分,二分图判定是在点双连通分量的基础上完成的

    int n,m,cnt,deep,cnt1,ans,top,sum;
    bool vis[maxn],vis1[maxn],vis2[maxn];
    int g[maxn],dfn[maxn],low[maxn],tmp1[maxn],col[maxn],co[maxn],st[maxn];
    int G[maxn][maxn];

    图G是原图,邻接矩阵存储,既可以避免重边又可以方便建补图

    然后cnt是边的数量,cnt1是临时存放的双连通分量中的点数,tmp1用来临时存放双连通分量,vis1用来判断点是否在双连通分量中

    co是二分图染色数组,vis2是统计删点的

    bool dfs(int u,int color)
    {
        //cout<<"###"<<u<<" "<<color<<endl; 
        co[u]=color;
        for(int tmp=g[u];tmp;tmp=e[tmp].next)  //遍历子图 
        {
            int v=e[tmp].t;
            if(vis1[v]==0) continue;  //判定该点是否属于当前连通分量
            if(co[v]!=-1)
            {
                if(co[v]==color) return false;
                continue;
            }
            if(!dfs(v,!color)) return false;
        }
        return true;
    }
    bool dfs(int u,int color)
    {
        //cout<<"###"<<u<<" "<<color<<endl; 
        co[u]=color;
        for(int tmp=g[u];tmp;tmp=e[tmp].next)  //遍历子图 
        {
            int v=e[tmp].t;
            if(vis1[v]==0) continue;  //判定该点是否属于当前连通分量
            if(co[v]!=-1)
            {
                if(co[v]==color) return false;
                continue;
            }
            if(!dfs(v,!color)) return false;
        }
        return true;
    }

    二分图DFS染色,代码风格不令人舒适

    void tarjan(int u,int fa)
    {
        //cout<<"###"<<u<<" "<<fa<<endl;
        low[u]=dfn[u]=++deep;
        vis[u]=1;
        st[++top]=u;
        for(int tmp=g[u];tmp;tmp=e[tmp].next)
        {
            int v=e[tmp].t;
            if(v==fa) continue;
            if(!dfn[v])
            {
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>=dfn[u])
                {
                    sum++;
                    int vn;
                    cnt1=0;
                    memset(vis1,0,sizeof(vis1));  //用于判断点是否在当前连通分量里 
                    do
                    {
                        vn=st[top--];
                        col[vn]=sum;
                        vis1[vn]=1;
                        tmp1[cnt1++]=vn;  //临时存点双连通分量 
                        vis[vn]=0;
                    }while(vn!=v);
                    vis1[u]=1;
                    memset(co,-1,sizeof(co));
                    if(!dfs(u,0))  //对点双连通分量进行二分图判定 
                    {
                        vis2[u]=1;  //是二分图,参与记录结果 
                        while(cnt1--) vis2[tmp1[cnt1]]=1;
                    }
                    //top--;
                }
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
        }
    }

    Tarjan求点双连通分量+做文章,标记好了vis1之后,在处理的时候扫补图,然后特判一下,连通子图就出来了

    下面给出完整的实现:

      1 #include<cstdio>
      2 #include<cstring>
      3 #include<algorithm>
      4 using namespace std;
      5 const int maxn=1005;
      6 const int maxm=1000005;
      7 int n,m,cnt,deep,cnt1,ans,top,sum;
      8 bool vis[maxn],vis1[maxn],vis2[maxn];
      9 int g[maxn],dfn[maxn],low[maxn],tmp1[maxn],col[maxn],co[maxn],st[maxn];
     10 int G[maxn][maxn];
     11 struct Edge{int t,next;}e[maxm];
     12 void addedge(int u,int v)
     13 {
     14     e[++cnt].t=v;e[cnt].next=g[u];
     15     g[u]=cnt;
     16 }
     17 bool dfs(int u,int color)
     18 {
     19     //cout<<"###"<<u<<" "<<color<<endl; 
     20     co[u]=color;
     21     for(int tmp=g[u];tmp;tmp=e[tmp].next)  //遍历子图 
     22     {
     23         int v=e[tmp].t;
     24         if(vis1[v]==0) continue;  //判定该点是否属于当前连通分量
     25         if(co[v]!=-1)
     26         {
     27             if(co[v]==color) return false;
     28             continue;
     29         }
     30         if(!dfs(v,!color)) return false;
     31     }
     32     return true;
     33 } 
     34 void tarjan(int u,int fa)
     35 {
     36     //cout<<"###"<<u<<" "<<fa<<endl;
     37     low[u]=dfn[u]=++deep;
     38     vis[u]=1;
     39     st[++top]=u;
     40     for(int tmp=g[u];tmp;tmp=e[tmp].next)
     41     {
     42         int v=e[tmp].t;
     43         if(v==fa) continue;
     44         if(!dfn[v])
     45         {
     46             tarjan(v,u);
     47             low[u]=min(low[u],low[v]);
     48             if(low[v]>=dfn[u])
     49             {
     50                 sum++;
     51                 int vn;
     52                 cnt1=0;
     53                 memset(vis1,0,sizeof(vis1));  //用于判断点是否在当前连通分量里 
     54                 do
     55                 {
     56                     vn=st[top--];
     57                     col[vn]=sum;
     58                     vis1[vn]=1;
     59                     tmp1[cnt1++]=vn;  //临时存点双连通分量 
     60                     vis[vn]=0;
     61                 }while(vn!=v);
     62                 vis1[u]=1;
     63                 memset(co,-1,sizeof(co));
     64                 if(!dfs(u,0))  //对点双连通分量进行二分图判定 
     65                 {
     66                     vis2[u]=1;  //是二分图,参与记录结果 
     67                     while(cnt1--) vis2[tmp1[cnt1]]=1;
     68                 }
     69                 //top--;
     70             }
     71         }
     72         else if(vis[v]) low[u]=min(low[u],dfn[v]);
     73     }
     74 }
     75 void init()
     76 {
     77     cnt=deep=cnt1=top=sum=0;
     78     memset(vis,0,sizeof(vis));
     79     memset(vis1,0,sizeof(vis1));
     80     memset(vis2,0,sizeof(vis2));
     81     memset(g,0,sizeof(g));
     82     memset(dfn,0,sizeof(dfn));
     83     memset(low,0,sizeof(low));
     84     memset(tmp1,0,sizeof(tmp1));
     85     memset(col,0,sizeof(col));
     86     memset(co,0,sizeof(co));
     87     memset(st,0,sizeof(st));
     88     memset(G,0,sizeof(G));
     89     memset(e,0,sizeof(e));
     90 }
     91 int main()
     92 {
     93     int u,v;
     94     while(scanf("%d%d",&n,&m)==2)
     95     {
     96         init();
     97         if(n==0&&m==0) break;
     98         while(m--)
     99         {
    100             scanf("%d%d",&u,&v);
    101             G[u][v]=G[v][u]=1;
    102         }
    103         for(int i=1;i<=n;i++)
    104             for(int j=1;j<=n;j++)
    105                 if(i!=j&&G[i][j]==0) addedge(i,j);
    106         for(int i=1;i<=n;i++)
    107             if(!dfn[i]) tarjan(i,-1);
    108         ans=n;
    109         for(int i=1;i<=n;i++)
    110             if(vis2[i]) ans--;
    111         printf("%d
    ",ans);
    112     }
    113     return 0;
    114 }

    讲道理这个题,综合性强,而且挺难的

  • 相关阅读:
    Java复习笔记8--内部类
    Java复习笔记7--Java封装
    Java复习笔记6--Final&Static
    Java复习笔记5--equals方法总结
    Java复习笔记4--实现多重继承
    Java复习笔记3--强制类型转换
    Java复习笔记2--匿名类和多态
    python版本管理--pyenv
    gensim自然语言处理(续)
    gensim自然语言处理
  • 原文地址:https://www.cnblogs.com/aininot260/p/9433433.html
Copyright © 2011-2022 走看看