E - Rook Path Editorial by KoD
\(k\) 回の操作後にルークが \((i, j)\) に置かれているようにする方法の総数を \(f_{k, i, j}\) とおきます。\(f_{K, x_2, y_2}\) が答えとなります。
\(i = x_1\) かつ \(j = y_1\) のとき \(f_{0, i, j} = 1, \) そうでないときは \(f_{0, i, j} = 0\) です。\(k > 0\) については、以下の関係が成り立ちます。
- \(f_{k, i, j} = \sum_{x \neq i} f_{k - 1, x, j} + \sum_{y \neq j} f_{k - 1, i, y}\)
この値の計算を愚直に行うと、\(\Theta (N^3 K)\) の時間計算量を要するため、\(f\) の特徴を利用して計算を高速化する必要があります。
実際に \(f_{k, i, j}\) を計算してみると、多くの値が等しくなっており、各 \(k\) ごとに \(f_{k, i, j}\) の値の種類は高々 \(4\) 通りであることに気づくでしょう。状態数を圧縮して計算するため、グリッド上のマスの集合を以下の \(4\) つに分けます。
- \(S_{0, 0} = \{ (x_2, y_2) \}\)
- \(S_{0, 1} = \{ (x_2, y) \, \vert \, y \neq y_2 \}\)
- \(S_{1, 0} = \{ (x, y_2) \, \vert \, x \neq x_2 \}\)
- \(S_{1, 1} = \{ (x, y) \, \vert \, x \neq x_2 \land y \neq y_2 \}\)
\(g_{k, a, b} = \sum_{(i, j) \in S_{a, b}} f(k, i, j)\) と定義します。すると、\(k > 0\) に対し以下の関係が成り立ちます。
- \(g_{k, 0, 0} = g_{k - 1, 0, 1} + g_{k - 1, 1, 0}\)
- \(g_{k, 0, 1} = (Y - 1) g_{k - 1, 0, 0} + (Y - 2) g_{k - 1, 0, 1} + g_{k - 1, 1, 1}\)
- \(g_{k, 1, 0} = (X - 1) g_{k - 1, 0, 0} + (X - 2) g_{k - 1, 1, 0} + g_{k - 1, 1, 1}\)
- \(g_{k, 1, 1} = (X - 1)g_{k - 1, 0, 1} + (Y - 1)g_{k - 1, 1, 0} + (X - 2 + Y - 2) g_{k - 1, 1, 1}\)
\(g_{K, 0, 0}\) が答えとなります。計算量は \(O(K)\) です。
実装例 (C++) :
#include <bits/stdc++.h>
#include <atcoder/modint>
using namespace std;
using namespace atcoder;
using Fp = modint998244353;
int main() {
int h, w, k;
cin >> h >> w >> k;
array<array<Fp, 2>, 2> dp = {};
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
dp[x1 != x2][y1 != y2] = 1;
array<Fp, 2> row = {1, h - 1}, col = {1, w - 1};
while (k--) {
array<array<Fp, 2>, 2> next = {};
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
for (int a = 0; a < 2; ++a) {
next[i][a] += dp[i][j] * (col[a] - (j == a));
next[a][j] += dp[i][j] * (row[a] - (i == a));
}
}
}
dp = move(next);
}
cout << dp[0][0].val() << '\n';
return 0;
}
余談 : 行列累乗などを用いて \(O(\log K)\) で解くこともできます。
posted:
last update: