• [BZOJ5317][JSOI2018]部落战争(闵可夫斯基和)


    对于点集$A$,$B$,闵可夫斯基和$C={(x1+x2,y1+y2)|(x1,x2)in A,(y1,y2)in B}$。
    由此可知,对于两个凸包$A$,$B$的闵可夫斯基和$C$满足,$C$中的向量是所有$A$中向量与$B$中向量的和的并集。可以证明,$C$也是一个凸包。
    现在问题是要求,对于询问向量$vec{d}$,是否存在$vec{a}in A$,$vec{b}in B$,使得$vec{a}=vec{b}+vec{d}$。
    移项得$vec{d}=vec{a}-vec{b}$,发现这是$B$中所有向量取反后与$A$的闵可夫斯基和。于是问题转化为,求$A$与$-B$的闵可夫斯基和$C$,并快速判断某个向量是否在$C$内。
    求闵可夫斯基和有一个线性算法,正确性不会证明。
    先求出$A$和$B$的凸包,再取出凸包上所有的边,这些边显然都分别是已经按极角排好序的向量。
    初始时$C$只有一个点$vec{a1}+vec{b1}$,其中$a1$,$b1$分别是$A$和$B$凸包中的第一个点。
    接下来类似归并排序地,按极角序从小到大依次插入新向量并维护凸包。
    最后,凸包的尾部可能会出现一些多余点,直接删除或再求一次凸包即可。
    得到凸包$C$后,问题只剩快速判断一个点$P$是否在凸包$C$内了。
    很简单,将凸包中点以相对于凸包第一个点的极角序排好,二分找到$P$在其中哪个位置,向量叉积即可判断。
    总复杂度$O(nlog n)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     4 typedef long long ll;
     5 using namespace std;
     6 
     7 const int N=200010;
     8 int n,m,q,tot;
     9 struct P{ ll x,y; }s,p[N],p1[N],p2[N],v1[N],v2[N],t[N],a[N];
    10 P operator +(const P &a,const P &b){ return (P){a.x+b.x,a.y+b.y}; }
    11 P operator -(const P &a,const P &b){ return (P){a.x-b.x,a.y-b.y}; }
    12 ll operator *(const P &a,const P &b){ return a.x*b.y-a.y*b.x; }
    13 ll len(P a){ return a.x*a.x+a.y*a.y; }
    14 
    15 bool cmp(const P &a,const P &b){ ll k=(a-s)*(b-s); return k ? k>0 : len(a-s)<len(b-s); }
    16 
    17 int Graham(P a[],int n){
    18     rep(i,1,n) t[i]=a[i];
    19     rep(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]);
    20     s=t[1]; sort(t+2,t+n+1,cmp); int top=1;
    21     rep(i,2,n){
    22         while (top>1 && (t[top]-t[top-1])*(t[i]-t[top-1])<=0) top--;
    23         t[++top]=t[i];
    24     }
    25     rep(i,1,top) a[i]=t[i];
    26     return top;
    27 }
    28 
    29 bool jud(P x){
    30     P vx=x-p[1];
    31     if (vx*(p[tot]-p[1])<0 || vx*(p[2]-p[1])>0) return 0;
    32     int px=lower_bound(p+2,p+tot+1,x,cmp)-p-1;
    33     return (x-p[px])*(p[px%tot+1]-p[px])<=0;
    34 }
    35 
    36 int main(){
    37     freopen("bzoj5317.in","r",stdin);
    38     freopen("bzoj5317.out","w",stdout);
    39     scanf("%d%d%d",&n,&m,&q);
    40     rep(i,1,n) scanf("%lld%lld",&p1[i].x,&p1[i].y);
    41     rep(i,1,m) scanf("%lld%lld",&p2[i].x,&p2[i].y),p2[i].x=-p2[i].x,p2[i].y=-p2[i].y;
    42     n=Graham(p1,n); m=Graham(p2,m);
    43     rep(i,1,n-1) v1[i]=p1[i+1]-p1[i]; v1[n]=p1[1]-p1[n];
    44     rep(i,1,m-1) v2[i]=p2[i+1]-p2[i]; v2[m]=p2[1]-p2[m];
    45     p[1]=p1[1]+p2[1]; tot=1;
    46     int l1=1,l2=1;
    47     while (l1<=n || l2<=m)
    48         tot++,p[tot]=p[tot-1]+((l1<=n && (l2>m || (v1[l1]*v2[l2]>=0))) ? v1[l1++] : v2[l2++]);
    49     while (tot>1 && (p[tot]-p[tot-1])*(p[1]-p[tot-1])<=0) tot--;
    50     s=p[1]; P qs;
    51     rep(i,1,q) scanf("%lld%lld",&qs.x,&qs.y),printf("%d
    ",jud(qs));
    52     return 0;
    53 }
  • 相关阅读:
    c# HexStringtoByte十六进制字符串转字节与modusCRC校验
    安卓app_sl4_4星级评分条示范代码
    安卓app_sl3_28同意显示开始按钮示范
    安卓app_sl3_27通过ImageView显示带边边框的图片
    关于命令RGZPFM
    (转)经典中的经典
    Shell 截取文件名和后缀
    漫话:如何给女朋友解释什么是CDN?
    使用Shell遍历目录及其子目录中的所有文件
    Linux tr命令使用方法
  • 原文地址:https://www.cnblogs.com/HocRiser/p/10268249.html
Copyright © 2020-2023  润新知