本番中なんとか解けたけど、すごくグチャグチャしたん。
問題概要
すぬけ君は、 日間にわたって、毎日次のようなパズルを解こうとしている。
すぬけ君の持っている端末に、縦 行、横 列からなる格子状の盤面が毎日配信されてくる。それぞれのマスは白または黒で塗られている。ただし、 日目の盤面は、 日目に配信される盤面の上から 行目で左から 列目のマスの白黒が反転したものである。
個分の盤面に対し、
- はじめに最大 1 回、長方形領域を指定してその領域内の白黒を反転
- 好きな回数だけ、好きな行か列を選んで白黒を反転することを繰り返す
という操作を行い、すべての盤面を白にすることができるかどうかを判定せよ。
制約
解法
いかにも「日々の盤面変化が小さいこと」を利用しそうな問題である。
まず、最初の「最大 1 回長方形領域を指定する」がなかった場合を考える。まず、行や列を選んで白黒反転させる操作は
- 盤面内のどの 2 × 2 の正方形内の白の個数の偶奇を変えない
ということがわかる。逆に少し考えると、どの 2 × 2 の正方形内を見ても白の個数が偶数であるならば、全部白にできることが少し考えるとわかる。そこでこの問題を、
- 盤面を 2 × 2 の正方形すべてについて「白の個数」の偶奇をとったもの
の世界で考えることにする。このとき、この変換した世界において
- 毎日ごとの の反転はどうなるか
- 「長方形領域で反転」はどうなるか
を考える。前者はマス を含む正方形は 4 個あるので、その 4 個の偶奇を変えればよい。後者は
- 長方形状の 4 点の偶奇を変える
- x 座標または y 座標の等しい 2 点の偶奇を変える
- 1 点の偶奇を変える
のいずれかになることを示せる。こうすると、与えられた盤面を「長方形領域反転」によって全部偶数にできるかどうかを判定することができる。
実装状は「奇数のマス」を常に set で管理するようにした。奇数のマスが 5 個以上だったらそもそも "NO" で、4 個以下のときはそれが「長方形状の 4 点」「x 座標または y 座標が等しい 2 点」「1 点」「0 点」のいずれかであれば "YES" になる。計算量は
- 最初の盤面変換
- クエリ
という感じになる。
#include <iostream> #include <vector> #include <set> #include <cstring> #include <algorithm> using namespace std; const int MAX = 2100; int ori[MAX][MAX]; int fi[MAX][MAX]; typedef pair<int,int> pint; int main() { int N, M, Q; cin >> N >> M >> Q; memset(ori, 0, sizeof(ori)); memset(fi, 0, sizeof(fi)); for (int i = 0; i < N; ++i) { string str; cin >> str; for (int j = 0; j < str.size(); ++j) { if (str[j] == '#') ori[i][j] = 1; } } int num = 0; set<pint> se; for (int i = 0; i < max(N-1, 1); ++i) { for (int j = 0; j < max(M-1, 1); ++j) { int con = 0; for (int i2 = 0; i2 < 2; ++i2) { for (int j2 = 0; j2 < 2; ++j2) { if (ori[i+i2][j+j2] == 1) ++con; } } fi[i][j] = con % 2; if (fi[i][j]) { ++num; se.insert(pint(i, j)); } } } for (int q = 0; q < Q; ++q) { if (se.size() <= 1) cout << "Yes" << endl; else if (se.size() == 2) { vector<pint> v; for (auto it = se.begin(); it != se.end(); ++it) v.push_back(*it); if (v[0].first == v[1].first) cout << "Yes" << endl; else if (v[0].second == v[1].second) cout << "Yes" << endl; else cout << "No" << endl; } else if (se.size() == 3) cout << "No" << endl; else if (se.size() == 4) { vector<pint> v; for (auto it = se.begin(); it != se.end(); ++it) v.push_back(*it); sort(v.begin(), v.end()); if (v[1].first != v[0].first) cout << "No" << endl; else if (v[2].first == v[0].first) cout << "No" << endl; else if (v[3].first != v[2].first) cout << "No" << endl; else if (v[1].second == v[0].second) cout << "No" << endl; else if (v[2].second != v[0].second) cout << "No" << endl; else if (v[3].second != v[1].second) cout << "No" << endl; else cout << "Yes" << endl; } else cout << "No" << endl; if (q == Q-1) break; int x, y; cin >> x >> y; --x, --y; for (int xx = max(x-1, 0); xx <= x; ++xx) { for (int yy = max(y-1, 0); yy <= y; ++yy) { if (xx >= max(N-1, 1)) continue; if (yy >= max(M-1, 1)) continue; if (fi[xx][yy] == 0) ++num, se.insert(pint(xx, yy)); else --num, se.erase(pint(xx, yy)); fi[xx][yy] = 1 - fi[xx][yy]; } } } }
という問題を解けば良いことになる。