Official

D - Pond Editorial by mechanicalpenciI


この問題を二分探索で解くことを考えます。 すなわち「 \(N\times N\) のマス目に含まれる \(K\times K\) の区画であって、中央値が \(X\) 以下であるようなものは存在するか?」という問題を解くことを考えます。これに対する答えは、ある\(Y\)が存在して\(X\geq Y\)ならば Yes , \(X<Y\)ならば Noとなります。この\(Y\)が答えです。以下、\(X\)の値を固定して考えます。

中央値というのは直接扱うには少し難しい値でありますが、今回の定義からこの問題は、「 \(N\times N\) のマス目に含まれる \(K\times K\) の区画であって、その区画の中に \(X\) より大きいマスが \(\left\lfloor\frac{K^2}{2}\right\rfloor+1\) 個未満であるようなものは存在するか?」と書き換えられます。ここで「\(X\) より大きいマス」とは、\(A_{i,j} > X\) をみたすマス \((i,j)\) のことです。ただし、 \((i,j)\) で上から \(i\) 行目、左から \(j\) 列目のマスを 表します。

この問題を愚直に全探索して解こうとすると \(\Theta(N^2K^2)\) かかってしまいます。しかしこれは、条件をみたすマスを数える問題であり、条件をみたすならば \(1\) , みたさないならば \(0\) を割り当てたとするとこれは各区画ごとに和を取る問題という事になります。 このような問題については二次元累積和が有効です。 二次元累積和については検索すると分かりやすい解説が多く出ているのでそちらも参考にしてください。 今回の問題については以下のようにします。 実際は正方形でなくても使う事が出来ます。

\(S_{i,j}\) を、 \((1,1)\) を左上の角、 \((i,j)\) を右下の角とする長方形の区画に含まれるマス\((i', j')\)であって \(A_{i',j'} >X\) をみたすマスの数とし、まずこれを求めることを考えます。最初に便宜上 \(S_{0,i}=S_{i,0}=S_{0,0}=0\) とします。(これは \(0\)\(i\) 列などのつぶれた長方形の区画と考えればよいです。) その後、 \(S_{i,j}\)\(i ,j\) について昇順に

\[ S_{i,j}= \begin{cases} S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+1 & (A_{i,j}>X\ のとき) \\ S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1} & (A_{i,j}\leq X\ のとき) \end{cases} \]

と計算すれば求まります。これは図などを書くと分かりやすいです。
次に、 \((P,Q)\) を左上のマスとし、\((P',Q')\) を右下のマスとするような長方形の区画 ( \(P\leq P', Q\leq Q'\) ) 内で \(A_{i,j} >X\) であるようなマスの個数は \(S_{P',Q'}+S_{P-1,Q-1}-S_{P',Q-1}-S_{P-1,Q'}\) で求まります。これもそれぞれの \(S_{i,j}\) がどの区画の中における条件をみたすマスの数を表すのかに注意して図を手元で書いてみると分かると思います。

これによって、 \((N-K+1)\times (N-K+1)\) 個ある \(K\times K\) の区画それぞれについて \(A_{i,j} > X\) であるようなマスの個数を \(O(1)\) で求めることができます。これらの中に \(\left\lfloor\frac{K^2}{2}\right\rfloor+1\) 未満のものが \(1\) つでもあれば Yes 、なければ No です。 \(X\)を固定したときの問題について、 \(S_{i,j}\) を求めるパートとその後の各区画について求めるパートのいずれも \(O(N^2)\) で出来ますので、全体で \(O(N^2)\) で解けました。

明らかに答えは \(0\)\(\max(A_i)\) の間にあるので、\(ng=-1,\ ok=\max(A_i)\)として、\(X=(ng+ok)/2\) とし、 Yesならば \(ok=X\) , Noならば \(ng=X\) とすることを \(ng+1=ok\) となるまで繰り返すと、このときの \(ok\) の値が答えとなります。この操作は高々 \(\lfloor\log_2 (\max(A_i))\rfloor+1\) 回で終了するので、 よって元の問題は\(O(N^2\log (\max(A_i)))\)で解けました。 これは十分高速です。

C++による実装例:

#include <bits/stdc++.h>
using namespace std;
 
#define N 805
#define MAX_A 1000000000
#define rep(i, n) for(int i = 0; i < n; ++i)
 
int main(void) {
	int n, k, lim;
	int a[N][N];
	int s[N][N];
	rep(i, N) {
		s[i][0] = 0;
		s[0][i] = 0;
	}
	int ng = -1;
	int ok = MAX_A;
	int mid;
	bool ext;
	cin >> n >> k;
	lim = ((k*k) / 2) + 1;
	rep(i, n) {
		rep(j, n)cin >> a[i][j];
	}
	while ((ng + 1) < ok) {
		mid = (ng + ok) / 2;
		rep(i, n) {
			rep(j, n) {
				s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j];
				if (a[i][j] > mid)s[i + 1][j + 1]++;
			}
		}
		ext = false;
		rep(i, n - k + 1) {
			rep(j, n - k + 1) {
				if ((s[i + k][j + k] + s[i][j] - s[i][j + k] - s[i + k][j]) < lim)ext = true;
			}
		}
		if (ext)ok = mid;
		else ng = mid;
	}
	cout << ok << endl;
	return 0;
}

posted:
last update: