kmjp's blog

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

yukicoder : No.2885 Range Triangle Collision Decision Queries

シンプルなテクに見えてあんまり使わないかも。
https://yukicoder.me/problems/no/2885

問題

二次元座標系において、N個の三角形が指定される。
i個めの三角形は、いずれも向きの同じ直角二等辺三角形で、(A[i],B[i]),(A[i]-D[i],B[i]-D[i]),(A[i]+D[i],B[i]-D[i])を結ぶ。

以下のクエリに答えよ。
S,L,Rの3値が指定される。S個目の三角形は、L個目~R個目のいずれとも正の面積の共通部分を持つか判定せよ。

解法

二次元で共通部分の判定をするのは大変。
そこで、三角形の各辺に直角となる、y=x、y=-x、x=0の3つの直線を考え、それぞれの射影を考える。
S個目の三角形の射影が、L個目~R個目の三角形の射影と共通部分を持てばいいので、最大値・最小値を取るSegTreeにより判定できる。

template<class V,int NV> class SegTree_1 {
public:
	vector<V> val;
	static V const def=-(1LL<<60);;
	V comp(V l,V r){ return max(l,r);};
	
	SegTree_1(){val=vector<V>(NV*2,def);};
	V getval(int x,int y,int l=0,int r=NV,int k=1) { // x<=i<y
		if(r<=x || y<=l) return def;
		if(x<=l && r<=y) return val[k];
		return comp(getval(x,y,l,(l+r)/2,k*2),getval(x,y,(l+r)/2,r,k*2+1));
	}
	void update(int entry, V v) {
		entry += NV;
		val[entry]=v;
		while(entry>1) entry>>=1, val[entry]=comp(val[entry*2],val[entry*2+1]);
	}
};
SegTree_1<ll,1<<20> Fmi[3],Fma[3];

int N,Q;
ll A[202020],B[202020],D[202020];
ll Cmi[3][202020],Cma[3][202020];


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N) {
		cin>>A[i]>>B[i]>>D[i];
		Cmi[0][i]=B[i]-D[i];
		Cma[0][i]=B[i];
		Cmi[1][i]=A[i]+B[i]-2*D[i];
		Cma[1][i]=A[i]+B[i];
		Cmi[2][i]=A[i]-B[i];
		Cma[2][i]=A[i]-B[i]+2*D[i];
		
		FOR(j,3) {
			Fmi[j].update(i,-Cma[j][i]);
			Fma[j].update(i,Cmi[j][i]);
		}
	}
	
	cin>>Q;
	while(Q--) {
		int S,L,R;
		cin>>S>>L>>R;
		S--,L--;
		int ok=1;
		FOR(j,3) {
			ll mi=Fma[j].getval(L,R);
			ll ma=-Fmi[j].getval(L,R);
			if(ma<=Cmi[j][S]||mi>=Cma[j][S]) ok=0;
		}
		if(ok) {
			cout<<"Yes"<<endl;
		}
		else {
			cout<<"No"<<endl;
		}
	}
}

まとめ

射影を取って一次元で共通部分を判定するの、思いつかなかった…。