Codeforces Round #194 Div1D Characteristics of Rectangles

問題
N*Mの行列が与えられます。
この行列の端っこにある行や列をいくつか削ることができます(削らなくてもいいです)。

うまい削り方をして、結果として得られる新しい行列(部分長方形)の、「四隅の4つの値の最小値」を最大にした時の値を求めてください。

例えば、

1 1 1
1 2 2
1 2 2

の場合は1行目と1列目を削って
2 2
2 2
の最小値2というのが最大です。

制約

(2 ≤ n, m ≤ 1000)
(0 ≤ (行列の各要素の値) ≤ 10^9)


解法
答えは必ず行列内の要素のどれかの値になり、制約から高々10^9なので、
二分探索で「ある値lim以上の最小値を持つ削り方は存在するのか」というのを探していくというのが有力な選択肢です。

しかしこれを愚直に判定すると(nm)^2くらいの時間が掛かってしまうので工夫が必要です。
これを高速化するためにbool good[m][m]というメモを使います。
どういうメモかというと、good[r1][r2]は
「これまで見てきた行の中には、r1番目とr2番目が両方ともlim以上な行がどこかにある」
という意味です。
goodは最初全部falseですが、1回目にr1とr2番目が両方lim以上な行に訪れるとtrueになり、
2回目の行で訪れると、四隅がlim以上という部分長方形が作れることが示せます。

プログラムを見るとループが3つあって一見O(n^3)のコードに見えますが、
goodの更新が高々n^2回なので、n^2回未満でこのループは必ず抜け出します。

これによって二分探索と合わせてO(n^2logn)の解法が実現できます。


プログラム

#include<iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include<string>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<sstream>
#include<algorithm>
#include<map>
#include<complex>
using namespace std;


#define li			long long int
#define rep(i,to)	for(int i=0;i<((int)(to));i++)
#define repp(i,start,to)	for(int i=(int)(start);i<((int)(to));i++)
#define pb			push_back
#define sz(v)		((li)(v).size())
#define bgn(v)		((v).begin())
#define eend(v)		((v).end())
#define allof(v)	(v).begin(), (v).end()
#define dodp(v,n)		memset(v,(li)n,sizeof(v))
#define bit(n)		(1ll<<(li)(n))
#define mp(a,b)		make_pair(a,b)
#define rin	rep(i,n)
#define EPS 1e-10
#define ETOL 1e-8
#define MOD 1000000007
#define p2(a,b)		cout<<a<<"\t"<<b<<endl
#define p3(a,b,c)		cout<<a<<"\t"<<b<<"\t"<<c<<endl

li a[1010][1010];
li n,m;

bool check(li lim){
	bool good[1010][1010];
	rep(i,1010)rep(j,1010)good[i][j]=false;
	rep(i,n){
		vector<li> pos;
		rep(j,m){
			if(a[i][j]>=lim)pos.pb(j);
		}
		rep(j,sz(pos)){
			repp(k,j+1,sz(pos)){
				if(good[pos[j]][pos[k]])return true;
				good[pos[j]][pos[k]]=true;
			}
		}
	}
	return false;
}

int main(){
	cin>>n>>m;
	vector<li> aa;
	
	rep(i,n){
		rep(j,m){
			cin>>a[i][j];
			aa.pb(a[i][j]);
		}
	}
	
	sort(allof(aa));
	vector<li>::iterator it=unique(allof(aa));
	aa.erase(it,aa.end());
	
	//bin search
	li l=0, r=sz(aa)-1;
	li mid=(l+r)/2;
	li res=0;
	while(l<=r){
		mid=(l+r)/2;
		if(check(aa[mid])){
			res=aa[mid];
			l=mid+1;
		}
		else r=mid-1;
	}
	
	cout<<res<<endl;
	
	return 0;
}