2019.02.22【JSOI2018】【BZOJ5317】【洛谷P4557】战争(凸包)(Minkowski Sum的凸包)

洛谷传送门

BZOJ传送门


解析;

首先可以很显然的发现,一个部落能够控制的点就是它的点集形成的凸包中的点。

先求两个凸包 A , B A,B A,B

首先将问题转化成数学语言:

给出一个向量 v ⃗ = ( d x , d y ) \vec{v}=(dx,dy) v =(dx,dy),询问是否 ∃ b ∈ B , b + v ⃗ ∈ A \exist b\in B,b+\vec{v}\in A bB,b+v A

进行一点小转化: a = b + v ⃗ a − b = v ⃗ a=b+\vec{v}\\a-b=\vec{v} a=b+v ab=v

就是说是否存在 a , b a,b a,b两个点向量,使得它们之间的向量等于询问向量。

好像仍然不是很清楚,所以我们将 B B B凸包横纵坐标全部乘上 − 1 -1 1,得到我们要询问的东西:

a + b = v ⃗ a+b=\vec{v} a+b=v

是否存在 A A A凸包中的点,使得其以某个凸包 B B B中的点向量为方向平移后等于询问向量。

介绍一个点集之间的运算,Minkowski Sum:

Minkowski Sum 的运算对象是两个点集,运算结果是一个点集,记两个点集 A , B A,B A,B的Minkowski Sum为点集 C C C,满足 C = { a + b ∣ a ∈ A , b ∈ B } C=\{a+b|a\in A,b\in B\} C={a+baA,bB}

显然刚才这个问题就是问 v ⃗ \vec{v} v 是否在 A , B A,B A,B凸包的Minkowski Sum的凸包中。证明如果不画图的有点麻烦,然而我这种手残党怎么可能画图,所以请各位自行画图理解。

要判断点是否在凸包内很简单,极角二分加特判就行了。

所以现在问题是怎么快速地求两个凸包的Minkowski Sum的凸包。

如果直接按照定义构造点集 C C C之后求凸包的话,复杂度是 O ( n m ( log ⁡ n + log ⁡ m ) ) O(nm(\log n+\log m)) O(nm(logn+logm))显然会 g g gg gg

但是我们不是要求Minkowski Sum,而是求凸包,考虑有没有什么性质可以利用。

连图都不用画我们就知道, A A A的最左点加上 B B B的最右点是什么用都没有的。

换句话说,我们只需要在每个方向上选择 A , B A,B A,B最靠近这个方向的点来构成凸包就行了。

由于凸包上的斜率是单调的,所有我们可以极角来进行拓扑排序,当然,实际操作不用极角,用叉积就行了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	inline int getint(){
		re char c;
		re bool f=0;
		while(!isdigit(c=gc()))if(c=='-')f=1;re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
}
using namespace IO;

cs double eps=1e-6;
struct Point{
	double x,y;
	Point(){}
	Point(cs double &_x,cs double &_y):x(_x),y(_y){}
	friend Point operator+(cs Point &a,cs Point &b){return Point(a.x+b.x,a.y+b.y);}
	friend Point operator-(cs Point &a,cs Point &b){return Point(a.x-b.x,a.y-b.y);}
	friend Point operator*(cs Point &a,cs double &b){return Point(a.x*b,a.y*b);}
	friend double operator*(cs Point &a,cs Point &b){return a.x*b.y-a.y*b.x;}
	friend double dot(cs Point &a,cs Point &b){return a.x*b.x+a.y*b.y;}
	double norm(){return sqrt(dot(*this,*this));}
};

inline int sign(double x){
	return fabs(x)<eps?0:(x>0?1:-1);
}

inline int convex_hull(Point *p,int n){
	for(int re i=2;i<=n;++i)
	if(p[i].x<p[1].x||(sign(p[i].x-p[1].x)==0&&p[i].y<p[1].y))swap(p[i],p[1]);
	sort(p+2,p+n+1,[&](cs Point &a,cs Point &b){
		if(sign((a-p[1])*(b-p[1])))return sign((a-p[1])*(b-p[1]))>0;
		return (a-p[1]).norm()<(b-p[1]).norm();
	});
	int m=1;
	for(int re i=2;i<=n;++i){
		while(m>=2&&sign((p[i]-p[m-1])*(p[m]-p[m-1]))>=0)--m;
		p[++m]=p[i];
	}
	return m;
} 

cs int N=100005;
int n,m,q,cnt;
Point p1[N],p2[N],p[N<<1],v1[N],v2[N];

inline void Minkowski(){
	p1[n+1]=p1[1];for(int re i=1;i<=n;++i)v1[i]=p1[i+1]-p1[i];
	p2[m+1]=p2[1];for(int re i=1;i<=m;++i)v2[i]=p2[i+1]-p2[i];
	p[1]=p1[1]+p2[1];cnt=1;
	re int q1=1,q2=1;
	while(q1<=n&&q2<=m)++cnt,p[cnt]=p[cnt-1]+((sign(v1[q1]*v2[q2])>=0)?v1[q1++]:v2[q2++]);
	while(q1<=n)++cnt,p[cnt]=p[cnt-1]+v1[q1++];
	while(q2<=m)++cnt,p[cnt]=p[cnt-1]+v2[q2++];
	while(cnt>1&&sign((p[cnt]-p[cnt-1])*(p[1]-p[cnt-1]))<=0)--cnt;
}

inline bool in(cs Point &q){
	Point v=q-p[1];
	if(sign(v*(p[cnt]-p[1]))<0||v*(p[2]-p[1])>0)return false;
	int pos=lower_bound(p+2,p+cnt+1,q,[&](cs Point &a,cs Point &b){
		if(sign((a-p[1])*(b-p[1])))return sign((a-p[1])*(b-p[1]))>0;
		return (a-p[1]).norm()<(b-p[1]).norm();
	})-p-1;
	return (q-p[pos])*(p[pos%cnt+1]-p[pos])<=0;
}

signed main(){
	n=getint();m=getint();q=getint();
	for(int re i=1;i<=n;++i)p1[i].x=getint(),p1[i].y=getint();
	n=convex_hull(p1,n);
	for(int re i=1;i<=m;++i)p2[i].x=-getint(),p2[i].y=-getint();
	m=convex_hull(p2,m);
	Minkowski();
	while(q--){
		int x=getint(),y=getint();
		cout<<in(Point(x,y))<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值