AOJ 1158 - ICPC: Intelligent Congruent Partition of Chocolate

解法

\(_{36}C_{18}\) は流石に間に合わない。
しかし、2つの領域を合同かつ連結になるように選んでいけば、探索空間はそんなに大きくないように思える(実際大きくない)。
なので、今探索中の領域1に連結なチョコレートを付け加えたときに、領域2にも対応する位置のチョコレートを使うと確定させるような探索が良さそう。
そのためにまず、領域2が領域1に対してどれだけ回転しているか&裏返しかを決めておく。
このもとで、一番左上のチョコレートを領域1として使うと確定させ、そのチョコレートが領域2においてどこに対応するかを全部試す。
すると、チョコレートを領域1に付け加えたとき、それが領域2のどこに対応するかがわかるようになる。
領域1にチョコレートを付け加えるときに、連結になるようにしておけば自動的に領域2も連結になるのでOK。
途中でバッティングしたり範囲外に出たりせず全部使い切れれば YES とすればよい。

ソースコード

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

constexpr int dy[4] = {0, 1, 0, -1};
constexpr int dx[4] = {1, 0, -1, 0};

int main() {
    int w, h;
    while(cin >> w >> h, w) {
        vector<vector<int>> r(h, vector<int>(w)), idx(h, vector<int>(w, -1));
        vector<int> xs, ys;
        for(int i = 0; i < h; ++i) {
            for(int j = 0; j < w; ++j) {
                cin >> r[i][j];
                if(r[i][j] == 1) {
                    idx[i][j] = ys.size();
                    ys.push_back(i);
                    xs.push_back(j);
                }
            }
        }
        const int n = xs.size();
        if(n & 1) {
            cout << "NO" << endl;
            continue;
        }

        auto in_range = [&] (int y, int x) {
            return 0 <= y && y < h && 0 <= x && x < w;
        };
        auto calc_dy = [] (int mir, int rot, int s_dy, int s_dx) {
            if(rot == 0)      return s_dy;
            else if(rot == 1) return (mir ? -s_dx : s_dx);
            else if(rot == 2) return -s_dy;
            else              return (mir ? s_dx : -s_dx);
        };
        auto calc_dx = [] (int mir, int rot, int s_dy, int s_dx) {
            if(rot == 0)      return (mir ? -s_dx : s_dx);
            else if(rot == 1) return s_dy;
            else if(rot == 2) return (mir ? s_dx : -s_dx);
            else              return -s_dy;
        };

        auto solve = [&] () {
            for(int j = 1; j < n; ++j) {
                for(int rot = 0; rot < 4; ++rot) {
                    for(int mir = 0; mir < 2; ++mir) {
                        unordered_set<ll> vis;
                        stack<pair<ll, ll>> st;
                        vis.insert(1);
                        st.emplace(1, 1LL << j);
                        while(!st.empty()) {
                            const ll used1 = st.top().first, used2 = st.top().second;
                            if(__builtin_popcountll(used1) * 2 == n) {
                                return true;
                            }
                            st.pop();
                            for(int k = 0; k < n; ++k) {
                                if(!(used1 & (1LL << k))) continue;
                                for(int d = 0; d < 4; ++d) {
                                    const int ny1 = ys[k] + dy[d], nx1 = xs[k] + dx[d];
                                    const int ny2 = ys[j] + calc_dy(mir, rot, ny1 - ys[0], nx1 - xs[0]);
                                    const int nx2 = xs[j] + calc_dx(mir, rot, ny1 - ys[0], nx1 - xs[0]);
                                    if(!in_range(ny1, nx1) || idx[ny1][nx1] == -1) continue;
                                    if(!in_range(ny2, nx2) || idx[ny2][nx2] == -1) continue;
                                    const int id1 = idx[ny1][nx1], id2 = idx[ny2][nx2];
                                    if((used1 | used2) & (1LL << id1) || (used1 | used2) & (1LL << id2) || id1 == id2) continue;
                                    if(vis.count(used1 | (1LL << id1))) continue;
                                    vis.insert(used1 | (1LL << id1));
                                    st.emplace(used1 | (1LL << id1), used2 | (1LL << id2));
                                }
                            }
                        }
                    }
                }
            }
            return false;
        };

        cout << (solve() ? "YES" : "NO") << endl;
    }
}

感想

国内予選ならではって感じの問題だった。