mayoko’s diary

プロコンとかいろいろ。

SRM 533 div1 med: MagicBoard

解法

見た目がハミルトンパスに見えますが, 実はオイラー路の問題になります。

蟻本の p205 に書いてあるようなグラフを作るようなイメージです。二部グラフ (U, W) を作るのですが, board[i][j] == '#' ならば, U の i 番目の頂点と, W の j 番目の頂点の間に辺を貼ります。

そうすると, 例えば「場所 (r, c0) から (r, c1) に移動する」というのは, W の c0 番目の頂点から U の r 番目の頂点に移動し, W の c1 番目の頂点に移動する, ということになります。
このようにグラフを表現すると,

  • すべての '#' を通らなければならない -> 二部グラフ (U, W) のすべての辺を通らなければならない(つまりオイラー路があるかを判定したい)
  • 偶数番目は横移動, 奇数番目は縦移動 -> (これも二部グラフであることと関連)オイラー路のはじめの頂点は W 側からはじめなければならない

となります。

struct UnionFind {
    vector<int> par;
    int n, cnt;
    UnionFind(const int& x = 0) {init(x);}
    void init(const int& x) {par.assign(cnt=n=x, -1);}
    inline int find(const int& x) {return par[x] < 0 ? x : par[x] = find(par[x]);}
    inline bool same(const int& x, const int& y) {return find(x) == find(y);}
    inline bool unite(int x, int y) {
        if ((x = find(x)) == (y = find(y))) return false;
        --cnt;
        if (par[x] > par[y]) swap(x, y);
        par[x] += par[y];
        par[y] = x;
        return true;
    }
    inline int count() const {return cnt;}
    inline int count(int x) {return -par[find(x)];}
};

class MagicBoard {
public:
    string ableToUnlock(vector <string> board) {
        const string yes = "YES", no = "NO";
        int n = board.size(), m = board[0].size();
        UnionFind uf(n+m);
        vector<vi> G(n+m);
        int memo = 0;
        for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
            if (board[i][j] == '#') {
                G[i].push_back(j+n);
                G[j+n].push_back(i);
                uf.unite(i, j+n);
                memo = i;
            }
        }
        for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
            if (board[i][j] == '#' && !uf.same(memo, i)) return no;
        }
        int cntr = 0, cntc = 0;
        for (int i = 0; i < n+m; i++) {
            if (G[i].size()%2) {
                if (i < n) cntr++;
                else cntc++;
            }
        }
        if (cntc+cntr == 0 || cntc+cntr == 2) {
            if (cntr < 2) return yes;
            else return no;
        }
        return no;
    }
};