poj 2942 LA3523  点-双…

这个题目的意思是:有一些骑士 , 需要开圆桌会议 , 但有一些骑士不能坐在一起 , 问最少有多少个骑士不能参加任何一个会议 。
注意:  不是开一次会议 , 而是开很多次会议 , 每次会议至少3个人 , 问有哪些骑士是任何一次都不能参加的 , 一个骑士可以参加多个会议。

这是一个无向点-双连通子图的问题 , 先把所有Bcc都求出来 , 然后判断那些bcc中的骑士超过3个人 , 并且人数奇数 , 对于判断 , 可以用判断一个图存不存在奇环 , 对于奇环 , 我们用交叉染色搜索奇圈的方法来判断当前双连通分量中是否存在奇圈  , 如果双连通分量中存在奇圈 , 那么我们就能确定 , 这个双连通分量中的所有骑士都能被安排坐下 , 因为如果这个双连通分量有偶数骑士 , 并且存在一个奇圈 , 那么我们确定肯定存在其他奇圈 , 如果这个双连通分量有奇数个骑士, 那么肯定存在一个环包含这个双连通分量中的所有骑士。

代码:

#include
#include
#include
#include
#include
using namespace std;

const int MAXN = 1010 ;

int pre[MAXN] , iscut[MAXN] , bccno[MAXN] , dfs_clock , bcc_cnt;
int n , m;
int A[MAXN][MAXN] ;
int color[MAXN] ;

vectorgrap[MAXN] , bcc[MAXN];
stacks , t;

int dfs(int u ,int fu)
{
      int lowu = pre[u] = ++dfs_clock;
      int child = 0;
      for(int i = 0 ; i < grap[u].size() ; i++)
      {
            int v = grap[u][i];
            if(!pre[v])
            {
                  s.push(u);
                  t.push(v);
                  child += 1;
                  int lowv = dfs(v , u);
                  if(lowu > lowv)  lowu = lowv;
                  if(lowv >= pre[u]) 
                  {
                        iscut[u] = true ;
                        bcc_cnt++;  bcc[bcc_cnt].clear();  // 清空
                        for(; ;)
                        {
                              int x = s.top() , y = t.top();
                              s.pop() ; t.pop();
                              if(bccno[x] != bcc_cnt)  {bcc[bcc_cnt].push_back(x) ; bccno[x] = bcc_cnt;}
                              if(bccno[y] != bcc_cnt)  {bcc[bcc_cnt].push_back(y) ; bccno[y] = bcc_cnt;}
                              if(x == u && y == v)    break;
                        }
                  }
            }
            else if(pre[v] < pre[u] && v != fu)
            {
                  if(lowu > pre[v])
                        lowu = pre[v];
                  s.push(u);
                  t.push(v);
            }
      }
      if(fu < 0 && child == 1)  iscut[u] = 0 ;
      return lowu;
}

bool bipartite(int u , int p)    // 判断这个bcc是不是二分图 , 利用黑白二着色
{
      for(int i = 0 ; i < grap[u].size() ; i++)
      {
            int v = grap[u][i] ;
            if(bccno[v] != p)  continue ;
            if(color[v] == color[u])  return false ;
            if(!color[v])
            {
                  color[v] = 3 - color[u];
                  if(!bipartite(v , p))  return false ;
            }
      }
      return true ;
}

int main()
{
      while(scanf("%d %d" , &n , &m) )
      {
            if(n ==0 && m==0)  break;
            int i = 0, j , x , y;
            memset(A , 0 , sizeof(A));
            memset(bccno , 0 , sizeof(bccno));
            memset(iscut , false , sizeof(iscut));
            memset(pre , 0 , sizeof(pre));
            for(i = 1 ; i <= n ; i++)  //  把邻接表中的点都清空
                  grap[i].clear();
            for(i = 0 ; i < m ; i++)
            {
                  scanf("%d %d" , &x , &y);
                  A[x][y] = A[y][x] = 1;
            }
            for(i = 1 ; i <= n ; i++)
                  for(j = i+1 ; j <= n ; j++)    //  把邻接矩阵转化为邻接表的形式
                  {
                        if(!A[i][j])
                        {
                              grap[i].push_back(j);
                              grap[j].push_back(i);
                        }
                  }
            dfs_clock = bcc_cnt = 0;
            for(i = 1; i <= n; i++)      //  因为可能题目给出的点不是在通一个连通图上 , 所以需要判断是不是所有点都已经判断了
                  if(!pre[i])
                  {
                        x = dfs(i , -1);
                  }
            int odd[MAXN];  //  标记哪些点事可能参加圆桌会议的
            memset(odd , 0 , sizeof(odd));
            for(i = 1; i <= bcc_cnt ; i++)
            {
                  memset(color , 0 , sizeof(color));
                  for(j = 0 ; j < bcc[i].size() ; j++)  bccno[bcc[i][j]] = i;//让割顶重新标记 , 因为割定可能是很多bcc的公共点
                  int u = bcc[i][0] ;
                  color[u] = 1;
                  if(!bipartite(u , i)) 
                        for(j = 0; j < bcc[i].size() ; j++)
                              odd[bcc[i][j]] = 1;        //记录哪些点是在奇圈中
                       
            }
            int ans = n;
            for(i = 1;  i <= n ; i++)  if(odd[i])  ans -= 1;
            cout<<ans<<endl;
      }
      return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值