NoiminのNoise

競技プログラミング (多め) とWeb (たまに) ,自然言語処理 (ブログではまだ)。数式の書き方を一気に KaTeX に変えようとして記事を全削除してインポートし直すなどしたので,過去にブックマークされた記事は URL が変わってしまっている可能性があります…….

Codeforces Round #641 (Div. 1 C / Div 2. E) Kamil and Making a Stream

問題: Problem - E - Codeforces

公式解説: Codeforces Round #641 Editorial - Codeforces

問題概要

$n \times m (1 \leq n, \mathit{m} \leq 1000)$ の行列 $A$ が与えられる。$i$ 行 $j$ 列の要素は $a_{i, j}$ であり,0 なら白,1 なら黒である。

この行列のすべての要素に対して,以下の操作を行う。

  • 上下左右に同じ色が存在しない要素の色はそのままにする
  • 上下左右のいずれかに同じ色が存在する要素の色を反転する

この操作について,次の $t (\leq 10^5)$ 個のクエリに答えよ。

  • すべての要素に対して操作を$p$ 回繰り返した後の $i$ 行 $j$ 列の要素の色を答えよ。

解法概要

今回の問題では,色がそのままになる条件が厳しく,色が変わる条件は緩い。条件に従って Sample 3 なんかを手でやってみると,操作を行っても色が変わらないエリアと操作ごとに色が変わるエリアがあると考えたときに,色が変わるエリアが徐々に広がる。

操作前 1回目の操作後 2回目の操作後
01011
10110
01101
11010
10101
01000
10000
00001
00010
00101
11111
11111
11111
11110
11101
3回目の操作後 4回目の操作後 5回目の操作後
00000
00000
00000
00000
00001
11111
11111
11111
11111
11111
00000
00000
00000
00000
00000

もう少し詳しく見てみると,「隣に同じ色の要素がある (= 次の操作で色が変わる)」要素との最短距離に応じて,色が変わらない状態→色が毎回変化する状態へと変化するタイミングが決まることがわかる。

数学的帰納法っぽく証明してみる。

隣に同じ色がある要素との最短距離が 0 ,つまり自分の隣に自分と同じ色があるような要素の場合はあと 0 回で 毎回色が変化する状態」になる。つまりもうすでに色が毎回変化する状態になっている。

また,隣に同じ色がある要素との最短距離が $d$ の要素 $a_{i, j}$があり,$d$ 回の操作後に毎回色が変化するようになると仮定する。さらに,その要素の隣の要素に,隣に同じ色がある要素との最短距離が $d+1$ の要素 $a_{i', j'}$があったとする。

$d$ 回の操作後,$a_{i, j}$ はまだ色は変化していないが, $d+1$ 回目の操作後から毎回色が変化する,つまり4近傍に色が同じ色要素が存在する状態になっている。一方,$a_{i', j'}$ はまだ色が変化しておらず,まだ4近傍に同じ色の要素がないので $d+1$ 回目の操作後も色が変化しない。 (仮定より,$a_{i, j} \neq a_{i', j'}$ が必ず成り立っている)

したがって,「隣に同じ色の要素がある」要素との最短距離が「その要素が後何回の操作で色が毎回変化する状態になるか」である。

クエリの数より,クエリの度に「隣に同じ色の要素がある」要素からの距離を求めていては間に合わない。また,「隣に同じ色の要素がある」要素は最大 $10^6$ 個あるので,BFS を $10^6$ 回やって距離の最小値を取るわけにもいかない。

しかしこの場合は,「隣に同じ色の要素がある」要素すべてからの距離がわかる必要はない。最短距離さえ分かっていれば良い。そのため,BFS を始めるときに「隣に同じ色の要素がある」要素をすべて queue に突っ込み,最短距離がわかった要素は二度訪れないようにすれば良い (多点 BFS や多始点 BFS などと言われている) 。

ソースコード

#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>

using namespace std;

using ll =  long long;
using Pii = pair<int, int>;

constexpr ll LINF = 1LL << 60;
constexpr int dyx[4][2] = {
    { 0, 1}, {-1, 0}, {0,-1}, {1, 0}
};

int n, m; ll t;

bool check(int y, int x) {
    return (0 <= y) && (y < n) && (0 <= x) && (x < m);
}

int main() {
    cin >> n >> m >> t;
    vector<vector<ll>> a(n, vector<ll>(m));
    for(int i=0;i<n;++i) {
        string s;
        cin >> s;
        for(int j=0;j<m;++j) {
            a[i][j] = (s[j] - '0');
        }
    }

    vector<vector<ll>> dist(n, vector<ll>(m, LINF));
    queue<Pii> que;
    for(int i=0;i<n;++i) {
        for(int j=0;j<m;++j) {
            for(int k=0;k<4;++k) {
                int y = i + dyx[k][0], x = j + dyx[k][1];
                if(check(y, x) && a[i][j] == a[y][x]) {
                    dist[i][j] = 0;
                    que.emplace(i, j);
                    break;
                }
            }
        }
    }

    while(!que.empty()) {
        Pii p = que.front(); que.pop();
        for(int k=0;k<4;++k) {
            int y = p.first + dyx[k][0], x = p.second + dyx[k][1];
            if(check(y, x) && dist[y][x] == LINF) {
                dist[y][x] = dist[p.first][p.second] + 1;
                que.emplace(y, x);
            }
        }
    }

    int y, x; ll p;
    for(int i=0;i<t;++i) {
        cin >> y >> x >> p;
        --y; --x;
        cout << (a[y][x] + max(0LL, p-dist[y][x])) % 2 << "\n";
    }

}

所感

コンテスト中に解けなかった問題をコンテスト後に自力 AC すると悔しいことが知られている。

それはそれとして,問題自体は好きです。