E - Integers on Grid Editorial by leaf1415
マス \((i, j)\) に書かれた整数を \(A[i][j]\) で表します。
動的計画法によってこの問題を解きます。
\(i = 1, 2, \ldots, N\) について、\(\mathrm{dp}[i]\) を下記の通りに定義します。
\(\mathrm{dp}[i] := \)(現在、コマがマス \((r_i, c_i)\) に置かれているとき、それ以降に高橋君がコマの移動を行うことができる回数の最大値)
本問題は、すべての \(i = 1, 2, \ldots, N\) について \(\mathrm{dp}[i]\) の値を求める問題です。よって、これを求める方法を以下で述べます。
現在コマがマス \((r_i, c_i)\) に置かれているとき、コマをマス \((r_j, c_j)\) に移動させることができる必要十分条件は、下記の \(2\) つをともに満たすことです。
- \(A[r_i][c_j] > A[r_i][c_i]\)
- マス \((r_j, c_j)\) はマス \((r_i, c_i)\) と同じ行にある、または、マス \((r_j, c_j)\) はマス \((r_i, c_i)\) と同じ列にある。すなわち、\(r_j = r_i\) または \(c_j = c_i\)
そのようなマス \((r_j, c_j)\) が存在しなければ、マス \((r_i, c_i)\) から移動できるマスはないため、\(\mathrm{dp}[i] = 0\) です。
そのようなマスが存在する場合は、「コマをそのマスに移動した後、それ以降に行うことができる移動の回数が最大となる」ようなマスを移動先に選ぶのが最適です。つまり、
\(\mathrm{dp}[i] = \max\lbrace \mathrm{dp}[j] + 1\) \( | \) マス \((r_i, c_i)\) からマス \((r_j,c_j)\) に移動可能 \(\rbrace\)
です。上記の漸化式を用いることで、すべての \(i = 1, 2, \ldots, N\) について \(\mathrm{dp}[i]\) の値を求めることができます。
ここで、上記の漸化式を用いて \(\mathrm{dp}[i]\) を計算する際には、マス \((r_i, c_i)\) から移動可能なすべてのマス \((r_j, c_j)\) の \(\mathrm{dp}[j]\) の値がすでに求まっている必要があることに注意してください。
このことに関しては、マス \((r_i, c_i)\) からマス \((r_j, c_j)\) に移動可能であるのは \(A[r_i][c_i] < A[r_j][c_j]\) の場合のみに限られるので、\(A[r_i][c_i]\) が大きい \(i\) から順番に \(\mathrm{dp}[i]\) を求めることで解決します。
すなわち、下記のアルゴリズムによって、すべての \(i = 1, 2, \ldots, N\) について \(\mathrm{dp}[i]\) の値を求めることができます。
正整数 \(X\) の降順に以下を行う:
- \(A[r_i][c_i] = X\) を満たすすべてのマス\((r_i, c_i)\)について、 \(\mathrm{dp}[i] \leftarrow \max\lbrace \mathrm{dp}[j] + 1\) \( | \) マス \((r_i, c_i)\) からマス \((r_j,c_j)\) に移動可能 \(\rbrace\) とする。 ただし、\((r_i, c_i)\) から移動可能なマスが存在しない場合は \(\mathrm{dp}[i] \leftarrow 0\)とする。
しかし、上記のアルゴリズムでは最悪の場合に \(\Theta(N^2)\) 時間かかり、実行制限時間に間に合わせることは絶望的です。したがって、これを高速化することを以下で考えます。
ある固定された正整数 \(X\) について、上記アルゴリズム中の
\(A[r_i][c_i] = X\) を満たすすべての \(i\) について、 \(\mathrm{dp}[i] \leftarrow \max\lbrace \mathrm{dp}[j] + 1\) \( | \) マス \((r_i, c_i)\) からマス \((r_j,c_j)\) に移動可能 \(\rbrace\) とする。
を行うことを考えます。 マス \((r_i, c_i)\) から移動可能なマスは、マス \((r_i, c_i)\) と同じ行または同じ列にあるため、\(\mathrm{dp}[i]\) を計算する際には下記の \(2\) つの値がわかっていれば、\(\mathrm{dp}[i]\) はこれら \(2\) つの値の最大値として \(O(1)\) 時間で求められます。
- 「マス \((r_i, c_i)\) から移動可能なマス \((r_j, c_j)\) のうち \(r_i\) 行目にあるもの」の中での \(\mathrm{dp}[j]+1\) の最大値、すなわち、\(\max \lbrace \mathrm{dp}[j] + 1 \) | \(A[r_j][c_j] > X, r_j = r_i \rbrace\)
- 「マス \((r_i, c_i)\) から移動可能なマス \((r_j, c_j)\) のうち \(c_i\) 列目にあるもの」の中での \(\mathrm{dp}[j]+1\) の最大値、すなわち、\(\max \lbrace \mathrm{dp}[j] + 1 \) | \(A[r_j][c_j] > X, c_j = c_i \rbrace\)
したがって、\(2\) つの配列 \(\mathrm{rmax}[\ast], \mathrm{cmax}[\ast]\) を準備し、\(X\) の降順に \(\mathrm{dp}[\ast]\) の値を求めていく過程において、
- \(r = 1, 2, \ldots H\) のそれぞれについて、\(\mathrm{rmax}[r] = \max \lbrace \mathrm{dp}[j] + 1 \) | \(A[r_j][c_j] > X, r_j = r\rbrace\)
- \(c = 1, 2, \ldots W\) のそれぞれについて、\(\mathrm{cmax}[c] = \max \lbrace \mathrm{dp}[j] + 1 \) | \(A[r_j][c_j] > X, c_j = c\rbrace\)
を満たす状態を維持することで、\(\mathrm{dp}[i]\) の値を \(\mathrm{dp}[i] \leftarrow \max(\mathrm{rmax}[r_i], \mathrm{cmax}[c_i])\) の通りに \(O(1)\) 時間で求められます。
すなわち、下記のアルゴリズムを用いると、すべての \(i = 1, 2, \ldots, N\) について \(\mathrm{dp}[i]\) の値を高速に求めることができます。
まず、各変数を次のように初期化する。
- すべての \(r =1, 2, \ldots, H\) について、\(\mathrm{rmax}[r] \leftarrow 0\) とする。
- すべての \(c =1, 2, \ldots, W\) について、\(\mathrm{cmax}[c] \leftarrow 0\)とする。
正整数 \(X\) の降順に以下を行う:
- まず、\(A[r_i][c_i] = X\) を満たすすべての \(i\) について、 \(\mathrm{dp}[i] \leftarrow \max(\mathrm{rmax}[r_i], \mathrm{cmax}[c_i])\) とする。
- その後、\(A[r_i][c_i] = X\) を満たすすべての \(i\) について、 \(\mathrm{rmax}[r_i] \leftarrow \max(\mathrm{rmax}[r_i], \mathrm{dp}[i] + 1), \mathrm{cmax}[r_i] \leftarrow \max(\mathrm{cmax}[r_i], \mathrm{dp}[i] + 1)\) とする。
以上より、この問題を \(O(N\log N + H + W)\) 時間で解くことができます。
以下に C++言語による実装例を記載します。
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int h, w, n;
int r[200005], c[200005], a[200005];
int rmax[200005], cmax[200005];
int dp[200005];
map<int, vector<int> > mp;
int main(void)
{
cin >> h >> w >> n;
for(int i = 1; i <= n; i++){
cin >> r[i] >> c[i] >> a[i];
mp[a[i]].push_back(i);
}
for(auto it = mp.rbegin(); it != mp.rend(); it++){
for(auto i : it->second) dp[i] = max(rmax[r[i]], cmax[c[i]]);
for(auto i : it->second){
rmax[r[i]] = max(rmax[r[i]], dp[i] + 1);
cmax[c[i]] = max(cmax[c[i]], dp[i] + 1);
}
}
for(int i = 1; i <= n; i++) cout << dp[i] << endl;
return 0;
}
posted:
last update: