説明

グラフの最大クリークを求める。一般グラフに対する最大クリークを求める問題はNP困難である。

補グラフに対する最大独立集合問題と等価である。

計算量

  • $O(2^{\sqrt {2M}} N)$

実装例

  • maximum_clique($g$, $f$):= 隣接行列 $g$ で表されるグラフのクリークを全列挙する。$f$ がクリークごとに呼び出され, そのとき渡されるvectorにはそのクリークを構成する頂点が格納されている。最終的に $f$ の戻り値のmaxが返される(verifyコード参照)。
template< typename T >
T maximum_clique(Matrix< bool > g, function< T(vector< int >) > f) {

  int N = (int) g.size(), M = 0;
  vector< int > deg(N), v(N);
  for(int i = 0; i < N; i++) {
    for(int j = 0; j < i; j++) {
      assert(g[i][j] == g[j][i]);
      if(g[i][j]) {
        ++deg[i];
        ++M;
      }
    }
  }
  T t = 0;
  int lim = (int) sqrt(2 * M);

  for(int i = 0; i < N; i++) {
    vector< int > notice;
    for(int j = 0; j < N; j++) {
      if(!v[j] && deg[j] < lim) {
        for(int k = 0; k < N; k++) {
          if(j == k) continue;
          if(g[j][k]) notice.emplace_back(k);
        }
        notice.emplace_back(j);
        break;
      }
    }
    if(notice.empty()) break;
    int neighbor = (int) notice.size() - 1;
    vector< int > bit(neighbor);
    for(int j = 0; j < neighbor; j++) {
      for(int k = 0; k < j; k++) {
        if(!g[notice[j]][notice[k]]) {
          bit[j] |= 1 << k;
          bit[k] |= 1 << j;
        }
      }
    }
    for(int j = 0; j < (1 << neighbor); j++) {
      bool ok = true;
      for(int k = 0; k < neighbor; k++) {
        if((j >> k) & 1) ok &= (j & bit[k]) == 0;
      }
      if(ok) {
        vector< int > stock{notice.back()};
        for(int k = 0; k < neighbor; k++) {
          if((j >> k) & 1) stock.emplace_back(notice[k]);
        }
        t = max(t, f(stock));
      }
    }
    v[notice.back()] = true;
    for(int j = 0; j < N; j++) {
      if(g[j][notice.back()]) {
        --deg[j];
        g[notice.back()][j] = g[j][notice.back()] = false;
      }
    }
  }

  vector< int > notice;
  for(int j = 0; j < N; j++) {
    if(!v[j]) notice.emplace_back(j);
  }
  int neighbor = (int) notice.size();
  vector< int > bit(neighbor);
  for(int j = 0; j < neighbor; j++) {
    for(int k = 0; k < j; k++) {
      if(!g[notice[j]][notice[k]]) {
        bit[j] |= 1 << k;
        bit[k] |= 1 << j;
      }
    }
  }
  for(int j = 0; j < (1 << neighbor); j++) {
    bool ok = true;
    for(int k = 0; k < neighbor; k++) {
      if((j >> k) & 1) ok &= (j & bit[k]) == 0;
    }
    if(ok) {
      vector< int > stock;
      for(int k = 0; k < neighbor; k++) {
        if((j >> k) & 1) stock.emplace_back(notice[k]);
      }
      t = max(t, f(stock));
    }
  }
  return t;
}

検証

AtCoder Beginner Contest 002 D - 派閥

int main() {
  int N, M;
  cin >> N >> M;
  Matrix< bool > g(N, vector< bool >(N));
  for(int i = 0; i < M; i++) {
    int x, y;
    cin >> x >> y;
    --x, --y;
    g[x][y] = true;
    g[y][x] = true;
  }
  function< int(vector< int >) > f = [](vector< int > t) { return t.size(); };
  cout << maximum_clique(g, f) << endl;
}

AOJ 2306 Rabbit Party

int main() {
  int N, M;
  cin >> N >> M;
  Matrix< bool > g(N, vector< bool >(N));
  Matrix< int > h(N, vector< int >(N));
  for(int i = 0; i < M; i++) {
    int x, y, z;
    cin >> x >> y >> z;
    --x, --y;
    g[x][y] = true;
    g[y][x] = true;
    h[x][y] = z;
    h[y][x] = z;
  }
  function< int(vector< int >) > f = [&](vector< int > t) {
    if(t.size() <= 1) return 0;
    int ret = 0;
    for(int i = 0; i < t.size(); i++) {
      int uku = 1 << 30;
      for(int j = 0; j < t.size(); j++) if(i != j) uku = min(uku, h[t[i]][t[j]]);
      ret += uku;
    }
    return ret;
  };
  cout << maximum_clique(g, f) << endl;
}

参考

指数時間アルゴリズム入門