BZOJ3548-[ONTAK2010]Party-Bron_Kerbosch极大团

说在前面

这个算法
之前在WC听zzx讲过…(反正现在忘掉了hhhh)
然后刷题刷见了,所以去学了学=w=
(然而蠢如me并不会复杂度证明,只知道正确性和做法)


题目

BZOJ3548传送门

题目大意

现在有 n n 个点,点与点间有两种关系(可以没有关系):

  1. 友好关系 Pi,j:选 i i 则必选 j

    • 敌对关系 Qi,j Q i , j :选 i i 则不能选 j
    • 询问最多可以选多少个点,以及选点最多的方案数
      范围: n250 n ≤ 250 n(n1)3|P|n(n1)2 n ( n − 1 ) 3 ≤ | P | ≤ n ( n − 1 ) 2 |Q|n(n1)6 | Q | ≤ n ( n − 1 ) 6

      输入输出格式

      输入格式:
      第一行三个整数 n,|P|,|Q| n , | P | , | Q | ,其中 |P| | P | 表示友好关系对数, |Q| | Q | 同理
      接下来 |P| | P | 行,每行两个数 u,v u , v ,描述一对友好关系
      再接下来 |Q| | Q | 行,格式同上,描述敌对关系

      输出格式:
      输出一行两个数,中间用空格隔开
      第一个数字是最多选点,第二个数字是方案


      解法

      首先可以注意到,友好关系是一个很强的限制,选一个点就要把一个连通块全部选中
      所以我们可以先对友好关系缩点,然后把那些内部有敌对关系的点从游戏中除外

      然后再把敌对关系的边建出来,发现这个问题就转化成了:任意图的最大独立集
      因为原图十分稠密,所以缩出来之后的点数很少(最多48个,这时有一堆孤立点以及一个完全图),就可以直接用Bron_Kerbosch算法求解了

      关于学习资料,可以参考(当然还有wiki)
      yefeng1627
      yo_bc
      DZYO

      算法主要就是用两个集合来加速搜索且去重,然后用性质剪枝
      关于为什么要么包含 u u ,要么包含 NonN(u) N(u) N ( u ) 是与 u u 相邻的点) ,这里是一点me的理解:
      首先 u 以及 N(u) N ( u ) 都是和已选所有点 有边的点
      如果说最优方案中存在 N(u) N ( u ) 中的某个点,而不存在 u u 。根据定义,u 和已选点,以及 N(u) N ( u ) 都有边,所以显然把 u u 加入后方案更优,所以假设不成立
      即:包含 N(u) 的最优方案 ,就要一定包含了 u u ,所以我们不如直接枚举 u,这样就节省了枚举 N(u) N ( u ) 的时间


      下面是代码

      #include <vector>
      #include <cstdio>
      #include <cstring>
      #include <algorithm>
      using namespace std ;
      
      int N , P , Q ;
      bool enemy[255][255] , mp[55][55] ;
      struct Union_Set{
          int fa[255] ;
          void init(){
              for( int i = 1 ; i <= N ; i ++ ) fa[i] = i ;
          }
          int find( int x ){
              return ( fa[x] == x ? x : ( fa[x] = find( fa[x] ) ) ) ;
          }
          bool Union( int a , int b ){
              a = find( a ) , b = find( b ) ;
              if( a == b ) return false ;
              fa[a] = b ; return true ;
          }
      } U ;
      
      int siz[55] , some[55][55] , none[55][55] , ans , cnt ;
      void dfs( int dep , int now , int scnt , int ncnt ){
          if( !scnt ){
              if( !ncnt ){
                  if( now > ans ) ans = now , cnt = 1 ;
                  else if( now == ans ) cnt ++ ;
              } return ;
          } int u = some[dep][1] ;
          for( int i = 1 ; i <= scnt ; i ++ ){
              int v = some[dep][i] , ns = 0 , nn = 0 ;
              if( mp[u][v] ) continue ;
              for( int j = 1 ; j < i ; j ++ )
                  if( mp[u][ some[dep][j] ]/* BAN but not vis */ && mp[v][ some[dep][j] ] )
                      some[dep+1][++ns] = some[dep][j] ;
              for( int j = i + 1 ; j <= scnt ; j ++ )
                  if( mp[v][ some[dep][j] ] ) some[dep+1][++ns] = some[dep][j] ;
              for( int j = 1 ; j <= ncnt ; j ++ )
                  if( mp[v][ none[dep][j] ] ) none[dep+1][++nn] = none[dep][j] ;
              dfs( dep + 1 , now + siz[v] , ns , nn ) ;
              none[dep][++ncnt] = v ;
          }
      }
      
      int id[255] , id_c ;
      vector<int> B[255] ;
      void solve(){
          for( int u = 1 ; u <= N ; u ++ )
              B[ U.find( u ) ].push_back( u ) ;
          for( int i = 1 ; i <= N ; i ++ ){
              int siz = B[i].size() ; bool ok = ( siz != 0 ) ;
              for( int j = 0 ; j < siz && ok ; j ++ )
                  for( int k = 0 ; k < siz && ok ; k ++ )
                      if( enemy[ B[i][j] ][ B[i][k] ] ) ok = false ;
              if( !ok ) continue ;
              ::siz[++id_c] = siz ;
              for( int j = 0 ; j < siz ; j ++ ) id[ B[i][j] ] = id_c ;
          }
          // complements :
          memset( mp , true , sizeof( mp ) ) ;
          for( int i = 1 ; i <= N ; i ++ ) if( id[i] )
              for( int j = i ; j <= N ; j ++ ) if( id[j] )
                  if( id[i] == id[j] || enemy[i][j] )
                      mp[ id[i] ][ id[j] ] = mp[ id[j] ][ id[i] ] = false ;
          for( int i = 1 ; i <= id_c ; i ++ ) some[0][i] = i ;
          dfs( 0 , 0 , id_c , 0 ) ;
          printf( "%d %d" , ans , cnt ) ;
      }
      
      int main(){
          scanf( "%d%d%d" , &N , &P , &Q ) , U.init() ;
          for( int i = 1 , u , v ; i <= P ; i ++ )
              scanf( "%d%d" , &u , &v ) , U.Union( u , v ) ;
          for( int i = 1 , u , v ; i <= Q ; i ++ ){
              scanf( "%d%d" , &u , &v ) ;
              enemy[u][v] = enemy[v][u] = true ;
          } solve() ;
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值