• poj 2942 Knights of the Round Table(点双连通分量+二分图判定)


    题目链接:http://poj.org/problem?id=2942

    题意:n个骑士要举行圆桌会议,但是有些骑士相互仇视,必须满足以下两个条件才能举行:

    (1)任何两个互相仇视的骑士不能相邻,每个骑士有两个相邻的骑士(即如果只有一个骑士,则不能举行会议)

    (2)圆桌会议坐下的骑士数量必须为奇数个

    有一张名单列出m个相互仇视的骑士,如果遵守以上两个规则,可能是某些骑士不可能被安排坐下,一种情况是一个骑士仇视所有其他的骑士。如果一个骑士不可能被安排坐下,则将他从骑士名单中剔除,问有多少个骑士会被剔除掉。

    1<=n<=100,1<=m<=1000000

    分析:题目上好像是说所有不被剔除的骑士都要参加圆桌会议,我严重怀疑这道题的题意是不是不明确,要不然就是poj的数据有问题。所以我将这题的题意理解为:不一定要所有不被剔除的骑士都要参加圆桌会议,只需其中的一部分就行了,这样有些数据就能解释为什么了。

    将骑士看成顶点,不互相仇视的骑士连边,建无向图,即建反图。构造无向图之后,先按要求(1),将所有能坐在一起的骑士分为一组,全部骑士分为若干组,每一组在图中是一个双连通分量。注意,这里我们要求的是点双连通分量,是点双,不是边双。为什么呢?因为我们是要剔除掉骑士,而骑士就是一个顶点了,并不是剔除掉仇恨关系,所以是点双。

    每一个双连通分量就是一个环了,但是这只是找到了环,而题目要求的是顶点数为奇数的环,即奇圈。

    那么怎么判断奇圈呢?这里有两个定理:

    (1)如果一个双连通分量中存在一个奇圈,那么该双连通分量内的所有顶点都处在某个奇圈内。

     在一个双连通分量中,必定存在一个圈经过该连通分量的所有节点,如果这个圈是奇圈,则该连通分量内所有的点都满足条件;若这个圈是偶圈,如果包含奇圈,则必定有另一个奇圈经过由剩下的点或该奇圈内至少2个点及其边构成的环。

    (2)一个双连通分量含有奇圈当且仅当它不是一个二分图。

     直观的想,对于一个二分图,从一个点出发要回到一个点显然要经过偶数个节点,所以肯定不存在奇圈。

    所以判断一个双连通分量是否含有奇圈,只需判断该双连通分量是否是二分图就行了,而判二分图可以用交叉染色法

    交叉染色法就是在DFS过程中反复交换着用两种不同的颜色对未染色过的点染色,若某次DFS中当前点的子节点和当前节点同色,则找到奇圈。

    想象一下二分图就像是河的两岸有两排节点,没染色一次就过河一次,那么相同颜色的节点必定在同一侧。一旦出现异侧有相同颜色的节点,就说明该图不是二分图了。

    总结一下:首先求出图的补图,然后把点双连通分量找出,对于每个双连通分量判断是否为二分图,如果不是则将分量重的所有点标记,统计一下标记过的顶点个数ans,最后结果就是n-ans。

    AC代码:

      1 #include<cstdio>
      2 #include<cstring>
      3 const int N=1000+5;
      4 struct EDGE{
      5     int v,next;
      6 }edge[N*N*2];
      7 int g,cnt,top,count,n,m;
      8 int first[N],low[N],dfn[N],sta[N*N*2],sm[N],map[N][N],color[N],part[N],mark[N];
      9 int min(int a,int b)
     10 {
     11     return a<b?a:b;
     12 }
     13 void AddEdge(int u,int v)     //建边
     14 {
     15     edge[g].v=v;
     16     edge[g].next=first[u];
     17     first[u]=g++;
     18 }
     19 int dfscol(int u,int col)   //交叉染色法
     20 {
     21     int i,v;
     22     color[u]=col;
     23     for(i=first[u];i!=-1;i=edge[i].next)
     24     {
     25         v=edge[i].v;
     26         if(!part[v])
     27             continue;
     28         if(color[v]==col)
     29             return 1;
     30         if(color[v]==0&&dfscol(v,-col))
     31             return 1;
     32     }
     33     return 0;
     34 }
     35 void color_solve()        //二分判定
     36 {
     37     int i;
     38     memset(part,0,sizeof(part));
     39     for(i=0;i<count;i++)
     40         part[sm[i]]=1;
     41     memset(color,0,sizeof(color));
     42     if(dfscol(sm[0],1))      //若含有奇圈
     43     {
     44         for(i=0;i<count;i++)
     45             mark[sm[i]]=1;
     46     }
     47 }
     48 void Tarjan(int u,int fa)    //求双连通分量
     49 {
     50     int i,v;
     51     low[u]=dfn[u]=++cnt;
     52     sta[top++]=u;
     53     for(i=first[u];i!=-1;i=edge[i].next)
     54     {
     55         v=edge[i].v;
     56         if(i==(fa^1))
     57             continue;
     58         if(!dfn[v])
     59         {
     60             Tarjan(v,i);
     61             low[u]=min(low[u],low[v]);
     62             if(low[v]>=dfn[u])
     63             {
     64                 count=0;          //将双连通分量记录起来。。刚开始这部分写错了,wa到死
     65                 sm[count++]=u;
     66                 sta[top]=-1;
     67                 while(sta[top]!=v)     //注意割点属于多个双连通分量,所以要弹到v,u不能弹出去
     68                 {
     69                     sm[count++]=sta[--top];
     70                 }
     71                 color_solve();     //判断该双连通分量是否为二分图
     72             }
     73         }
     74         else
     75             low[u]=min(low[u],dfn[v]);
     76     }
     77 }
     78 void solve()
     79 {
     80     int i,j,u,v;
     81     g=cnt=top=0;                 //初始化
     82     memset(low,0,sizeof(low));
     83     memset(dfn,0,sizeof(dfn));
     84     memset(first,-1,sizeof(first));
     85     memset(map,0,sizeof(map));
     86     memset(mark,0,sizeof(mark));
     87 
     88     for(i=0;i<m;i++)
     89     {
     90         scanf("%d%d",&u,&v);
     91         map[u][v]=map[v][u]=1;
     92     }
     93     for(i=1;i<=n;i++)         //建反图
     94         for(j=i+1;j<=n;j++)
     95         {
     96             if(!map[i][j])
     97             {
     98                 AddEdge(i,j);
     99                 AddEdge(j,i);
    100             }
    101         }
    102     for(i=1;i<=n;i++)        //求双连通分量
    103         if(!dfn[i])
    104             Tarjan(i,-1);
    105 
    106     int ans=0;
    107     for(i=1;i<=n;i++)       //统计已标记的顶点数
    108         if(mark[i])
    109             ans++;
    110     printf("%d
    ",n-ans);
    111 }
    112 int main()
    113 {
    114     while(scanf("%d%d",&n,&m))
    115     {
    116         if(n==0&&m==0)
    117             break;
    118         solve();
    119     }
    120     return 0;
    121 }
    View Code
  • 相关阅读:
    减少.NET应用程序内存占用的一则实践
    ASP.NET中检测图片真实否防范病毒上传
    PHP、Python 相关正则函数实例
    利用脚本将java回归到面向函数式编程
    ASP.Net 实现伪静态方法及意义
    ExecuteNonQuery()方法
    在.net中使用split方法!
    str.split()如何用?谢谢
    输出货币型
    (DataRowView)e.Item.DataItem只有在ItemDataBound这个事件中起效
  • 原文地址:https://www.cnblogs.com/frog112111/p/3342020.html
Copyright © 2020-2023  润新知