Codeforces 198(#125 Div1) E. Gripping Story

問題

座標平面上の(x, y)に宇宙船が固定されている。
最初、自分から距離r以下にある質量p以下の物体を回収できるマジックハンドをもっている。


宇宙空間にはn個のマジックハンドが散らばっており、
i番目のマジックハンドは座標(xi, yi)にあって、質量がmiであり、距離ri以下にある質量pi以下の物体を回収できる。
最初から持っているおよび回収したマジックハンドは、宇宙船から何度でも使うことができる。


最大でいくつのマジックハンドを回収できるか求めよ。

制約条件

n≦250000
座標とか質量とか≦10^9

方針

座標は関係ないので、全部宇宙船からの距離にしてしまってよい。
距離およびマジックハンドのパワーを座標圧縮する。


すると、マジックハンドは(距離, 質量)の二次元平面上の一点になる。
x座標を距離, y座標を質量で取る。
性能が(X, Y)のマジックハンドを回収すると、距離質量が(x, y)でx≦X, y≦Yなマジックハンドを全て回収できることになる。


回収したマジックハンドの性能が(X', Y')であるときに、別のマジックハンド(X, Y)で
X'≦X, Y'≦Yなものがあったら、前者のマジックハンドは完全に不要。
なので、マジックハンドはX座標の昇順に並べると、Y座標は減少列になる。
なのでこれを平衡二分木で管理する。


新たにマジックハンドを回収したとき、このギザギザの形を更新するのであるが、
平面上の点のうち、ギザギザが変わった部分の図形を、長方形に縦に区切って、それぞれの長方形にあるものだけを見てやるようにする。
長方形内部の点クエリは、range treeを使えばできる。


クエリが呼ばれる回数は、ギザギザがどれくらい更新されるかであるが、
一つのマジックハンドでギザギザの角は新たに高々3つしか増えない。
で、増えた角は高々1度しか減らないので、O(n)回しかこの長方形のクエリは来ないことになる。
range treeなので、一回の長方形のクエリはO(m + log^2n)(mは取り出す点の個数)で、
取り出せる点の個数は合計でn個しかなくて、全ての点は一度しか取り出されないので、
計算量はO(nlog^2n)になる。


なので、長方形の部分を全部処理して間に合う。

ソースコード

const int N = 250000;
int X, Y, P, R, n;
int x[N], y[N], m[N], d[N], p[N], r[N];

const int M = 1048576;
vector<pi> dat[2 * M - 1];

inline void query(int a, int b, int k, int l, int r, int lb, int ub, vi &v){
	if(b <= l || a >= r) return;
	if(a <= l && r <= b){
		int i = lower_bound(all(dat[k]), mp(lb, 0)) - dat[k].begin();
		int j = lower_bound(all(dat[k]), mp(ub + 1, 0)) - dat[k].begin();
		while(i < j) v.pb(dat[k][i++].second);
		return;
	}
	int m = (l + r) / 2;
	query(a, b, k * 2 + 1, l, m, lb, ub, v);
	query(a, b, k * 2 + 2, m, r, lb, ub, v);
}
inline vi query(int a, int b, int lb, int ub){//[a, b) x [lb, ub)の点を全て報告
	vi res;
	query(a, b, 0, 0, M, lb, ub, res);
	return res;
}

#define F first
#define S second

int ans;
set<pi> pts;
bool can[N];

void calc(int dist, int power){
	
	set<pi>::iterator it = pts.lower_bound(mp(dist, power));
	if(it != pts.end() && it->S >= power) return;
	
	pi dp(dist, power);
	vector<pi> er;
	vi vs;
	
	while(1){
		int y = it == pts.end() ? 0 : it->S;
		
		if(it != pts.begin()){
			--it;
			vi v = query(it->F, dist, y, power);
			vs.insert(vs.end(), all(v));
			if(it->S <= power){
				dist = it->F;
				er.pb(*it);
			}
			else break;
		}
		else{
			vi v = query(0, dist, y, power);
			vs.insert(vs.end(), all(v));
			break;
		}
	}
	each(i, er) pts.erase(*i);
	pts.insert(dp);
	each(i, vs) if(!can[*i]){
		ans++;
		can[*i] = 1;
		calc(r[*i] + 1, p[*i] + 1); //回収したマジックハンドで連鎖的に回収
	}
}

int main(){
	scanf("%d%d%d%d%d", &X, &Y, &P, &R, &n);
	vector<ll> ds;
	ds.pb((ll)R * R);
	
	rep(i, n){
		scanf("%d%d%d%d%d", x + i, y + i, m + i, p + i, r + i);
		x[i] -= X; y[i] -= Y;
		ds.pb((ll)x[i] * x[i] + (ll)y[i] * y[i]);
		ds.pb((ll)r[i] * r[i]);
	}
	sort(all(ds));
	ds.erase(unique(all(ds)), ds.end());
	R = lower_bound(all(ds), (ll)R * R) - ds.begin();
	
	rep(i, n){
		r[i] = lower_bound(all(ds), (ll)r[i] * r[i]) - ds.begin();
		d[i] = lower_bound(all(ds), (ll)x[i] * x[i] + (ll)y[i] * y[i]) - ds.begin();
		dat[d[i] + M - 1].pb(mp(m[i], i));
	}
	for(int i = M - 2; i >= 0; i--){
		int l = i * 2 + 1, r = i * 2 + 2;
		dat[i].resize(dat[l].size() + dat[r].size());
		merge(all(dat[l]), all(dat[r]), dat[i].begin());
	}
	
	calc(R + 1, P + 1);
	printf("%d\n", ans);
	
	return 0;
}