「SOL」戰爭(JSOI 洛谷)

一道融合了好多計算幾何技巧的題目ide


# 題面

> Linked 洛谷 P4557ui

給定兩組散點 \(A,B\),給定 \(Q\) 組詢問,每次詢問給出向量 \(v=(dx,dy)\)
求將 \(B\) 的散點所有移動 \(v\),獲得新的散點集 \(B'\),是否存在 \(A\) 中的三個點構成的三角形與 \(B\) 中的三點構成的三角形有公共點。spa

\(|A|,|B|\le 10^5\)\(Q\le10^5\)code


# 解析

「存在 \(A\) 中三點和 \(B\) 中三點,使得構成的三角形有公共點」,等價於 \(A\) 構成的凸包\(B\) 構成的凸包有公共點。由於凸包包含散點集中的任意一個點,也就包含散點集中任意三個點構成的三角形。blog

因而第一步很是天然的,分別求出 \(A,B\) 的凸包,Andrew或者Graham隨便,可是用Andrew比較好的是能夠刪掉凸包邊上的點。排序

Hint.rem

那麼在下面的解析中,默認 「$A,B$」 指的是散點集求得的凸包。get

怎麼判斷凸包有沒有交?若是時間複雜度比較寬鬆,就能夠檢測 \(A\) 的每一個頂點,看是否在 \(B\) 裏面,再檢測 \(B\) 的頂點是否在 \(A\) 裏面,這樣最優複雜度也只能是 \(O(n\log n)\) 一次。顯然不可過。input

因而就要用到一個黑科技,叫作閩可夫斯基和(Minkowski);兩個凸包 \(A,B\) 的閩可夫斯基和定義以下:string

\[\{(x_p+x_q,y_p+y_q)\mid p\in A,q\in B\} \]

能夠感覺到它的幾何意義就是「把凸包 \(B\) 沿着凸包 \(A\) 的邊緣平移一圈獲得的封閉圖形」,那麼顯然這個封閉圖形仍然是個凸包。舉個簡單的例子,將下圖的黑色凸包和綠色凸包作閔可夫斯基和就能夠獲得橙色凸包:

這個有什麼用?這是將兩個凸包「相加」,能不能兩個凸包「相減」

咱們把 \(B\) 以原點爲對稱中心對稱獲得 \(B^-\),那麼咱們對 \(A\)\(B^-\) 作閔可夫斯基和就是:

\[C=\{(x_p-x_q,y_p-y_q)\mid p\in A,q\in B\} \]

由於 \(A,B^-\) 都是凸包,因此 \(C\) 也是凸包。而 \(C\) 就很是有用了,若是原點包含在 \(\mathbf C\) ,那麼 \(A,B\) 就有交點。

因而第二步就是要對 \(A,B^-\) 求閔可夫斯基和獲得 \(C\)

怎麼求閔可夫斯基和?咱們只須要找到 \(C\) 的邊界,具體步驟以下:

  • 分別找到 \(A,B\) 的最左下角的點 \(p,q\)\(y\) 座標最小的前提下 \(x\) 座標最小);
  • \(C\) 的第一個點 \(w=p+q\)
  • 逆時針找凸包上與 \(p,q\) 相鄰的邊 \(\overrightarrow{E_p},\overrightarrow{E_q}\)
  • 比較 \(E_p,E_q\)極角,找到靠右的一條邊,好比說是 \(\overrightarrow{E_p}\)
  • \(w\) 移動到 \(w+\overrightarrow E_p\)
  • \(p\) 逆時針移動到下一個點;

這樣 \(p,q\) 一直移動就會將 \(A,B\) 的每一個點都遍歷到,而後每次獲得的 \(w\) 都是 \(C\) 邊界上的點,特殊處理一下能夠獲得 \(C\) 的頂點,具體能夠看代碼。

Hint.

下面簡記「凸包 $A$ 的每一個點移動向量 $v$」獲得的圖形爲 $A+v$。

以及對於兩點 $p,q$,記 $p\pm q=(x_p\pm x_q,y_p\pm y_q)$

再看一下題目的要求,即 \(A\)\(B+v\) 沒有交;根據閩可夫斯基和就是判斷

\[O\in \{p+q\mid p\in A,q\in(B^--v)\}\Leftrightarrow (O+v)\in\{p+q\mid A,q\in B^-\} \]

也就是判斷 \((O+v)\) 這個點在不在凸包 \(C\) 內了。

最後一步,對 \(C\) 進行極角排序,以下圖:

仍然是找到 \(C\) 的左下角的點 \(O\),而後向凸包的其餘點引出射線。二分找到 \((O+v)\) 所在的位置(位於哪兩條射線之間),而後用叉積判斷一下點在線段的哪一側便可。


# 源代碼

/*Lucky_Glass*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<algorithm>
using namespace std;

namespace GEO{
    typedef long long llong;
    const double EPS=1e-15;
    inline llong square(const int &key){return 1ll*key*key;}
    inline int sgn(const llong &key){
        if(!key) return 0;
        return key<0? -1:1;
    }
    inline int sgn(const int &key){
        if(!key) return 0;
        return key<0? -1:1;
    }
    inline int sgn(const double &key){
        if(fabs(key)<EPS) return 0;
        return key<0? -1:1;
    }
    struct Vector{
        int x,y;
        Vector(int _x=0,int _y=0):x(_x),y(_y){}
        friend double angle(const Vector &u,const Vector &v){
            return acos(double((long double)dot(u,v)/(long double)u.len()/(long double)v.len()));
        }
        friend llong dot(const Vector &u,const Vector &v){return 1ll*u.x*v.x+1ll*u.y*v.y;}
        friend llong cross(const Vector &u,const Vector &v){return 1ll*u.x*v.y-1ll*u.y*v.x;}
        double len()const{return sqrt(square(x)+square(y));}
    };
    struct Point{
        int x,y;
        Point(int _x=0,int _y=0):x(_x),y(_y){}
        Vector operator -(const Point &v)const{return Vector(x-v.x,y-v.y);}
        Point operator +(const Vector &v)const{return Point(x+v.x,y+v.y);}
        friend llong distPoint2(const Point &u,const Point &v){return square(u.x-v.x)+square(u.y-v.y);}
    };
    struct Line{
        Point p;Vector d;
        Line(){}
        Line(Point _p,Vector _d):p(_p),d(_d){}
        Line(Point s,Point t):p(s),d(t-s){}
    };
    //1=left / 0=on / -1=right
    int fixSide(const Point &s,const Point &t,const Point now){
        return sgn(cross(t-s,now-s));
    }
    bool cmpPointToX(const Point &u,const Point &v){
        return sgn(u.x-v.x)? sgn(u.x-v.x)<0:sgn(u.y-v.y)<0;
    }
    bool cmpPointToY(const Point &u,const Point &v){
        return sgn(u.y-v.y)? sgn(u.y-v.y)<0:sgn(u.x-v.x)<0;
    }
    void buildConvex(Point *org,int n,Point *res,int &nres){
        nres=0;
        sort(org,org+n,cmpPointToX);
        for(int i=0;i<n;i++){
            while(nres>1 && fixSide(res[nres-2],res[nres-1],org[i])<=0) nres--;
            res[nres++]=org[i];
        }
        int tmp=nres;
        for(int i=n-2;~i;i--){
            while(nres>tmp && fixSide(res[nres-2],res[nres-1],org[i])<=0) nres--;
            res[nres++]=org[i];
        }
        nres--;
    }
    void modelizeConvex(Point *org,int n){
        int it=0;
        for(int i=1;i<n;i++)
            if(cmpPointToY(org[i],org[it]))
                it=i;
        rotate(org,org+it,org+n);
    }
    void Minkowski(Point *pa,int na,Point *pb,int nb,Point *res,int &nres){
        //把凸包的左下角固定爲凸包的第一個元素
        modelizeConvex(pa,na),modelizeConvex(pb,nb);
        pa[na]=pa[0],pb[nb]=pb[0];
        int ma=0,mb=0;nres=0;
        Point now=Point(pa[0].x+pb[0].x,pa[0].y+pb[0].y);
        res[nres++]=now;
        while(ma<na && mb<nb){
            int re=sgn(cross(pa[ma+1]-pa[ma],pb[mb+1]-pb[mb]));
            //若是兩條邊極角相同,則一塊兒平移
            //這樣可使獲得的點都是凸包的頂點
            if(!re) now=now+(pa[ma+1]-pa[ma])+(pb[mb+1]-pb[mb]),ma++,mb++;
            else if(re>0) now=now+(pa[ma+1]-pa[ma]),ma++;
            else now=now+(pb[mb+1]-pb[mb]),mb++;
            res[nres++]=now;
        }
        while(ma<na){
            now=now+(pa[ma+1]-pa[ma]),ma++;
            res[nres++]=now;
        }
        while(mb<nb){
            now=now+(pb[mb+1]-pb[mb]),mb++;
            res[nres++]=now;
        }
        nres--;
    }
}
using namespace GEO;

const int N=1e5+10;

int nA,nB,nply,cas,mA,mB;
Point ply[N<<1],covA[N<<1],covB[N];

bool ifFarthar(const Point &u,const Point &v){
    return distPoint2(ply[0],u)<distPoint2(ply[0],v);
}
bool ifOutside(const Point &it){
    //有可能點不位於射線之間,先特判掉
    if(fixSide(ply[0],ply[1],it)<0 || fixSide(ply[0],ply[nply-1],it)>0) return true;
    if(fixSide(ply[0],ply[1],it)==0) return ifFarthar(ply[1],it);
    if(fixSide(ply[0],ply[nply-1],it)==0) return ifFarthar(ply[nply-1],it);
    Vector vit=it-ply[0];
    int lef=1,rig=nply-1;
    while(lef+1<rig){
        //用叉積判斷點在射線哪邊
        int mid=(lef+rig)>>1,re=sgn(cross(ply[mid]-ply[0],vit));
        //點在射線上
        if(!re) return ifFarthar(ply[mid],it);
        if(re<0) rig=mid;
        else lef=mid;
    }
    //判斷點在線段哪一側
    int re=sgn(cross(ply[lef]-it,ply[rig]-it));
    return re<0;
}
int main(){
    // freopen("input.in","r",stdin);
    scanf("%d%d%d",&nA,&nB,&cas);
    for(int i=0;i<nA;i++) scanf("%d%d",&ply[i].x,&ply[i].y);
    //先對 A,B 求凸包
    buildConvex(ply,nA,covA,mA);
    for(int i=0;i<nB;i++){
        scanf("%d%d",&ply[i].x,&ply[i].y);
        ply[i].x*=-1,ply[i].y*=-1;
    }
    buildConvex(ply,nB,covB,mB);
    //求出閔可夫斯基和
    Minkowski(covA,mA,covB,mB,ply,nply);
    for(int t=1;t<=cas;t++){
        Point mov;scanf("%d%d",&mov.x,&mov.y);
        //判斷點是否在凸包內
        printf("%d\n",!ifOutside(mov));
    }
    return 0;
}

THE END

Thanks for reading!

\[\begin{split} 「\ &夢のなかを歩きまわる\\ &\quad\small{\ 「能一直走在夢想之中}\\ &夜空を仰いで 星になるの\ 」\\ &\quad\small{仰望星空\ 成爲星星\ 」}\\ \\ 「\ &夢のなかを歩きまわる\\ &\quad\small{能一直走在夢想之中}\\ 「\ &夜空の星になるの\ 」\\ &\quad\small{成爲夜空中的星星\ 」}\\ ——&\text{《餘命3日少女(Cover)》By 米白} \end{split} \]

> Linked 餘命3日少女-網易雲

相關文章
相關標籤/搜索