kmjp's blog

競技プログラミング参加記です

Codeforces #187 Div1 D. Sereja and Straight Lines

これはDの割には簡単。
http://codeforces.com/contest/314/problem/D

問題

2次元座標上、N個の格子点の座標(X[i],y[i])が与えられる。

ここで、X軸と45度で交わり、互いに直角である2本の直線を引く。
この際、各格子点と2本いずれかの直線との距離の小さい方のうち、最大値を最小化せよ。
なお、点と直線の距離はユークリッド距離でなくマンハッタン距離で算出する。

解法

X軸と45度交わる、というのはわかりにくいので、X'=X+Y、Y'=X-Yで座標変換し、全体を45度傾ける。

求める答えをWとすると、縦に幅2Wの線を1本ひき、横に幅2Wの線を1本ひいたとき、全ての格子点をいずれかの線上に乗せられる最小のWを求めればよい。
Wは二分探索で求める。

まず格子点をX'[i]で昇順ソートしておく。
Wの縦線の左端を、あるX'[L]に合わせた場合、X'座標がX'[L]~X'[L]+2Wの範囲の格子点は縦線上に乗る。
縦線に乗る格子点の右端をX'[R]とすると、残された0~(L-1)番および(R+1)~(N-1)番の頂点が横線の上に乗ればWは条件を満たすことになる。

そのためには、事前にY'[0]~Y'[i]の最大値最小値と、Y'[j]~Y'[N-1]の最大値最小値を求めておけば、横線に乗るかどうかはO(1)で判定できる。

X[L]に対するRはlower_boundでも良いが尺取法で行った方が計算量が小さくて済む。
全体ではO(N*log(maxX))程度かな。

int N;
pair<ll,ll> P[100200];
ll miy[100200][2],may[100200][2];

int ok(ll w) {
	int x,y;
	
	y=0;
	FOR(x,N) {
		while(y<N && P[y].first <= P[x].first+w) y++;
		if(x==0 && y==N) return 1;
		if(x==0 && may[y][1]-miy[y][1]<=w) return 1;
		if(x>0 && y==N && may[x-1][0]-miy[x-1][0]<=w) return 1;
		if(x>0 && y<N && max(may[x-1][0],may[y][1])-min(miy[x-1][0],miy[y][1])<=w) return 1;
	}
	return 0;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N) {
		cin>>x>>y;
		P[i].first=x+y;
		P[i].second=x-y;
	}
	sort(P,P+N);
	miy[0][0]=may[0][0]=P[0].second;
	for(i=1;i<N;i++) miy[i][0]=min(miy[i-1][0],P[i].second);
	for(i=1;i<N;i++) may[i][0]=max(may[i-1][0],P[i].second);
	miy[N-1][1]=may[N-1][1]=P[N-1].second;
	for(i=N-2;i>=0;i--) miy[i][1]=min(miy[i+1][1],P[i].second);
	for(i=N-2;i>=0;i--) may[i][1]=max(may[i+1][1],P[i].second);
	
	ll ret=0;
	if(ok(0)) return _P("0\n");
	for(i=32;i>=0;i--) if(ok(ret+(1LL<<i))==0) ret+=1LL<<i;
	_P("%lf\n",(ret+1)/2.0);
	
}

まとめ

これマンハッタン距離である必要あるのかな。