题解 P4557 【[JSOI2018]战争】

WinXP

2018-07-06 15:41:58

Solution

~~写题解写到一半不小心弄没了体验极差~~ 所以首先我们可以发现这是一道~~可怜~~计算几何题 首先有个重点,题目说的是存在平面上的任何一个点被两个部落包含是不合法情况,而不是$(n+m)$个点中的任何一个被两个部落包含。 考虑一个部落包含的平面上的所有的点,它们其实是一个凸包的形状,这是很显然而且很容易想到的。我们考虑求出这个点集的凸包,把这个凸包三角剖分后,这若干个三角形显然能覆盖整个凸包;而对于一个不在凸包上的点,它与任意其他两个点(不管在不在凸包上)所形成的三角形显然会被这个凸包覆盖。 那么现在问题变成了这样的形式,也就是给出一个向量 $\vec{x}$ ,询问大小为 $m$ 的点集构成的凸包 $B$ 平移向量 $\vec{x}$ 后,与大小为 $n$ 的点集构成的凸包 $A$ 是否有交。 也就是 $$\exists\ b + \vec{x}=a(a\in A,b\in B)$$ 看一眼数据范围,本来我以为是$O(qn)$之类的东西,结果不是......果然做题的时候不能被数据结构误导... 我们考虑转化这个等式 $$\vec{x}\in\{a-b|(a\in A,b\in B)\}$$ 因为$a-b=a+(-b)$,所以把 $B$ 的所有点都取反,求的集合就变成了$\{a+b|(a\in A,b\in B)\}$。如何求这个东西呢? 先试想点集 $B$ 是一个点的情况,那么结果显然是 $A$ 的平移: ![](https://cdn.luogu.com.cn/upload/pic/22391.png) 那么再考虑 $B$ 是一条线段的情况,也不难想象应该是这样的: ![](https://cdn.luogu.com.cn/upload/pic/22392.png) 如果 $B$ 也是一个凸包,相信聪明的你可以知道,结果应该是这样的: ![](https://cdn.luogu.com.cn/upload/pic/22393.png) 这个东西其实可以看做是 $A$ 对于 $B$ 内的每一个点平移的并集。我们可以清楚的看到这个并集的形状是一个凸包,而且这个凸包内的所有的边都属于原来的 $A$ 和 $B$ 。注意这一点,这个性质很重要。当我们求出凸包 $A$ 和凸包 $B$ 后,按照凸包上的点滚一遍就能拥有凸包 $A$ 和凸包 $B$ 的所有边关于这个凸包最左面的点的角度的排序,接下来仿照归并排序的方式,把这些边按照角度顺序加入,就可以得到答案集合的所有点。这些点虽然是按顺序加入的,但是最后不一定是一个凸包,所以再求一遍就好了。现在要做的全部事情已经很显然了。 如果把 $A+B$ 定义为 $\{a+b|(a\in A,b\in B)\}$,这个东西实际上叫做`Minkowski sum`。~~我并不知道这是个什么东西也不会~~ 代码是~~抄~~仿照了[别的大佬的](https://blog.csdn.net/cdsszjj/article/details/80392221)(不过是我自己写的QAQ) 代码里的注释还是蛮详细的 ~~压行阅读无能?Astyle了解一下~~ ```cpp // luogu-judger-enable-o2 #include <bits/stdc++.h> #define rap(i,s,n) for(int i=s;i<=n;i++) #define drap(i,n,s) for(int i=n;i>=s;i--) #define N 111111 #define ll long long #define lf double #define m(s,k) memset(s,k,sizeof s) using namespace std; char xB[1<<15],*xS=xB,*xTT=xB; #define getc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++) #define isd(c) ((c>='0'&&c<='9')||(c=='-')) template<typename T> inline bool rd(T& xa){ char xchh; T f=1; while(xchh=getc(),(!isd(xchh))&&(xchh!=0)); if(xchh==0) return 0; if(xchh=='-') xchh=getc(),f=-1; xa=xchh-'0'; while(xchh=getc(),isd(xchh)) xa=xa*10+xchh-'0'; xa*=f; return 1; }//读入优化不用管 struct point{ ll x,y; point(){} point(ll _,ll __):x(_),y(__){} point operator + (const point& b) const {return point(x+b.x,y+b.y);} point operator - (const point& b) const {return point(x-b.x,y-b.y);}//顾名思义的加减 ll operator * (const point& b) const {return x*b.y-y*b.x;} //这个X乘,a x b如果小于0说明b在a的右面,否则b在a的左面。如果x为0说明a与b平行。 }t[N*2],p[N*2],p1[N],p2[N],v1[N],v2[N]; //大家都知道点和向量其实是一样的 所以这个point又是point又是vector 其中t,p,p1,p2是点,v1v2是向量 ll len(point a){return a.x*a.x+a.y*a.y;} point tss;//随便写的... bool cmp(const point& a,const point& b){ ll k=(a-tss)*(b-tss); if(k) return k>0; else return len(a-tss)<len(b-tss); }//因为差积可以判断左右 int graham(point *a,int n){ rap(i,1,n) t[i]=a[i]; rap(i,2,n) if(t[i].x<t[1].x||(t[i].x==t[1].x&&t[i].y<t[1].y)) swap(t[1],t[i]); tss=t[1]; sort(t+2,t+n+1,cmp); int top=1; rap(i,2,n){ while(top>1&&(t[top]-t[top-1])*(t[i]-t[top-1])<=0) top--; t[++top]=t[i]; //保证从开始的点出来是一个不断左转的过程 } rap(i,1,top) a[i]=t[i]; return top; //求凸包 } int n,m,q,cnt; bool init(point x){ //init=in it?判断是否在里面 并不是初始化(233) point vx=x-p[1]; if(vx*(p[cnt]-p[1])<0||vx*(p[2]-p[1])>0) return 0; long int px=lower_bound(p+2,p+cnt+1,x,cmp)-p-1; //按照排序规则直接二分搞出它的下面的点 //开始的时候忘了后面的-1WA了N发(MDZZ) return (x-p[px])*(p[px%cnt+1]-p[px])<=0; } int main(){ rd(n); rd(m); rd(q); rap(i,1,n) rd(p1[i].x),rd(p1[i].y); n=Graham(p1,n); rap(i,1,m) rd(p2[i].x),rd(p2[i].y),p2[i].x=-p2[i].x,p2[i].y=-p2[i].y; m=Graham(p2,m); //注意取反 rap(i,1,n-1) v1[i]=p1[i+1]-p1[i]; v1[n]=p1[1]-p1[n]; rap(i,1,m-1) v2[i]=p2[i+1]-p2[i]; v2[m]=p2[1]-p2[m]; //搞出所有的线段 p[1]=p1[1]+p2[1]; cnt=1; int l1=1,l2=1; while(l1<=n&&l2<=m) ++cnt,p[cnt]=p[cnt-1]+((v1[l1]*v2[l2]>=0)?(v1[l1++]):(v2[l2++])); while(l1<=n) ++cnt,p[cnt]=p[cnt-1]+v1[l1++]; while(l2<=m) ++cnt,p[cnt]=p[cnt-1]+v2[l2++]; //参照归并排序 while(cnt>1&&(p[cnt]-p[cnt-1])*(p[1]-p[cnt-1])<=0) cnt--; //因为后面有一些无用点 直接做凸包也可以不过这样常数小 tss=p[1]; point qx; rap(i,1,q) rd(qx.x),rd(qx.y),printf("%d\n",init(qx)); return 0; } ```