POJ 2723 Get Luffy Out

http://poj.org/problem?id=2723

https://vjudge.net/problem/POJ-2723

問題概要

  • $2N$種類の鍵があり、$0, 1, 2, \cdots, 2N-1$の番号が付いている。これは$N$個のペアに分割されており、ペアの片方のみ選んで持ち込むことができる。

  • $M$階の牢獄があり、$i$階($1\leq i \leq M$)に入るには扉を開ける必要がある。それぞれの扉には鍵穴が2つあり、$A_i,B_i$いずれか一方の鍵さえあれば開けることができる。

  • 最大でいくつのドアを開けられるか

  • なお、階のショートカットはできない(1,3階の扉が開けられても、2階の扉は開けられないなら答えは2つ)

制約

  • $1\leq N\leq 2^{10}$
  • $1\leq M\leq 2^{11}$

解法メモ

  • 「$x$階までの扉を全て開けられるか?」を二分探索

  • これは2-SATでできる

  • 具体的には、各キーを持っていくかどうかをTrue/Falseで表す。

  • キーの各ペアについてどちらかがFalseである必要がある

  • 各扉についてはorがTrueである必要がある

実装例

TLが厳しいので、普段使っているSCCクラスで余計な計算(強連結成分を潰したDAGを求めるなど)をしていると通らなかった

#include <algorithm>
#include <cassert>
#include <cstdio>
#include <iostream>
#include <vector>
#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
using namespace std;

struct SCC {
   private:
    int v;
    vector<vector<int> > G;
    vector<vector<int> > rG;
    vector<int> vs;
    vector<bool> used;
    bool built;

    void dfs(int u) {
        used[u] = true;
        for (int i = 0; i < G[u].size(); ++i) {
            int to = G[u][i];
            if (!used[to]) {
                dfs(to);
            }
        }
        vs.push_back(u);
    }

    void rdfs(int u, int k) {
        used[u] = true;
        _component[u] = k;
        for (int i = 0; i < rG[u].size(); ++i) {
            int to = rG[u][i];
            if (!used[to]) rdfs(to, k);
        }
    }

    vector<int> _component;
    // vector<vector<int> > _scc;
    // vector<vector<int> > _dag;

   public:
    void add_edge(int from, int to) {
        G[from].push_back(to);
        rG[to].push_back(from);
    }

    SCC() : v(0), built(false) {}
    SCC(int v) : v(v), built(false) {
        G = vector<vector<int> >(v);
        rG = vector<vector<int> >(v);
        used = vector<bool>(v);
        _component = vector<int>(v);
    }

    void build() {
        if (built) return;
        fill(used.begin(), used.end(), false);
        vs.clear();
        for (int u = 0; u < v; ++u)
            if (!used[u]) dfs(u);
        fill(used.begin(), used.end(), false);
        int k = 0;
        for (int i = int(vs.size() - 1); i >= 0; --i)
            if (!used[vs[i]]) rdfs(vs[i], k++);
        built = true;
    }
    // const vector<vector<int> >& scc() const {
    //     assert(built);
    //     return _scc;
    // }
    const vector<int>& component() const {
        assert(built);
        return _component;
    }
    // const vector<vector<int> >& dag() const {
    //     assert(built);
    //     return _dag;
    // }
};
struct twosat {
   private:
    int n;
    bool built;
    vector<bool> _answer;
    SCC scc;

   public:
    twosat() : built(false) {}
    twosat(int n) : n(n), built(false), scc(2 * n) {}
    void add_clause(int i, bool f, int j, bool g) {
        assert(0 <= i && i < n);
        assert(0 <= j && j < n);
        scc.add_edge(2 * i + int(!f), 2 * j + int(g));
        scc.add_edge(2 * j + int(!g), 2 * i + int(f));
    }
    void add_clause(int i, bool f) { add_clause(i, f, i, f); }
    bool satisfiable() {
        built = true;
        scc.build();
        _answer.resize(n);
        for (int i = 0; i < n; i++) {
            if (scc.component()[2 * i] == scc.component()[2 * i + 1]) return false;
            _answer[i] = scc.component()[2 * i] < scc.component()[2 * i + 1];
        }
        return true;
    }
    vector<bool> answer() {
        assert(built);
        return _answer;
    }
};

int n, m;
vector<pair<int, int> > pairs;
vector<pair<int, int> > doors;
bool cmp(int x) {
    twosat ts(n * 2);
    rep(i, n) {
        int a = pairs[i].first, b = pairs[i].second;
        ts.add_clause(a, 0, b, 0);
    }
    rep(i, x) {
        int a = doors[i].first, b = doors[i].second;
        ts.add_clause(a, 1, b, 1);
    }
    return ts.satisfiable();
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    while (true) {
        cin >> n >> m;
        if (n == 0 && m == 0) break;

        pairs = vector<pair<int, int> >(n);
        doors = vector<pair<int, int> >(m);
        rep(i, n) {
            int a, b;
            cin >> a >> b;
            pairs[i] = make_pair(a, b);
        }
        rep(i, m) {
            int a, b;
            cin >> a >> b;
            doors[i] = make_pair(a, b);
        }

        int ok = 0, ng = m + 1;
        while (abs(ng - ok) > 1) {
            int mid = (ng + ok) / 2;
            if (cmp(mid)) ok = mid;
            else ng = mid;
        }

        cout << ok << "\n";
    }
}