Codeforces 420 Coder-Strike 2014 - Finals (online edition, Div. 1) E. Playing the ball

問題

座標平面上にn個の円がある。
原点から好きな方向にボールを投げる。
ボールは距離dの地点でバウンドし、2*dでバウンドし…とk*dでバウンドして無限に跳ぶ。

円の内部または周でバウンドすると1点もらえる。
もらえる得点の最大値を求めよ。

制約条件

n≦2*10^4
5≦d≦10
円の半径≦50
中心の絶対値は10万以下
原点が円の一部であることはない

方針

平面走査。
半径k*dの円と、それぞれの円が交わる場所で、
角度が小さいほうで+1, 角度が大きい方で-1をする。


これを角度の順にソートして累積和を取れば、
跳ねる回数が変化する地点それぞれで跳ねる回数がわかる。


各円で跳ねる回数は高々10回なので、交点は20個以下。
なのでこの方針で間に合う。

ソースコード

inline double fix(double a){
	if(a >= 2 * PI) a -= 2 * PI;
	if(a < 0) a += 2 * PI;
	return a;
}

int n, d;
P c[20000];
int r[20000];

int main(){
	scanf("%d%d", &n, &d);
	
	vector<pair<double, int> > event;
	
	rep(i, n){
		
		int x, y;
		scanf("%d%d%d", &x, &y, r + i);
		c[i] = P(x, y);
		
		double dist = abs(c[i]);
		double lim = dist + r[i] + EPS;
		
		for(int k = (int)ceil((dist - r[i]) / d - EPS) * d; k < lim; k += d){
			
			double t = (dist * dist + k * k - r[i] * r[i]) / 2 / dist / k;
			t = acos(t);
			
			double a = fix(arg(c[i]) - t);
			double b = fix(arg(c[i]) + t);
			
			event.pb(mp(a - EPS, 1));
			event.pb(mp(b + EPS, -1));
			if(a > b) event.pb(mp(-EPS, 1));
		}
	}
	
	//rep(i, event.size()) cerr<<event[i].first<<" "<<event[i].second<<endl;
	
	sort(all(event));
	int ans = 0;
	int cur = 0;
	
	rep(e, event.size()){
		
		cur += event[e].second;
		ans = max(ans, cur);
	}
	cout << ans << endl;
	
	return 0;
}