1. 程式人生 > 其它 >Linux下配置ip地址四種方法

Linux下配置ip地址四種方法

前言

這篇東西大部分都是在瞎bb,大佬們可以選擇不看。

需要先學一點點線性代數的內容。

由於本人太菜,這篇部落格只會討論二維的情況。

由於本人太懶,因此會缺少一些示意圖。

向量

點積/數量積

一般用 \(\vec a \cdot \vec b\) 表示,有 \(\vec a \cdot \vec b = |\vec a||\vec b|\cos \left<\vec a, \vec b\right> = x_ax_b+y_ay_b\)

其中 \(\left<\vec a, \vec b\right>\) 表示 \(\vec a, \vec b\) 的夾角大小。

簡單的應用:

判斷向量垂直\(\vec a \bot \vec b \Longleftrightarrow \vec a\cdot \vec b = 0\)

判斷向量貢獻\(\vec a // \vec b \Longleftrightarrow \vec a\cdot \vec b = |\vec a||\vec b|\)

判斷向量夾角\(\cos\left<\vec a, \vec b\right> = \frac{\vec a \cdot \vec b}{|\vec a||\vec b|}\)

叉積

\[\mathrm{Cross}(\vec a,\vec b) = |\vec a||\vec b|\sin\left<\vec a, \vec b\right> = x_ay_b-x_by_a \]

非常有用,可以用於判斷 \(\vec a\)

旋轉到 \(\vec b\) 選用順指標轉還是逆時針轉。同時,兩個向量的叉積的絕對值等於這兩個向量夾成的平行四邊形的面積。

\(\mathrm{PS}\):叉積實際上是一個二階行列式。

接下來我們將叉積簡寫成 \(\vec a\times \vec b\)

叉積擁有分配律,結合律(和行列式相同)。

叉積用於判斷旋轉的具體操作如下:

  • 叉積 \(>0\),則從 \(\vec a\) 旋轉到 \(\vec b\) ,用逆時針的旋轉角度最小。
  • 叉積 \(<0\),則從 \(\vec a\) 旋轉到 \(\vec b\) ,用順時針的旋轉角度最小。
  • 叉積 \(=0\),則 \(\vec a, \vec b\)
    共線。

此外,叉積可以用判斷一個點 \(P\) 在直線 \(l\) 的哪一側。具體而言,隨便找到直線上的兩個不同的點 \(A,B (A,B\in l)\),然後作向量差 \(\vec a = A - B, \vec b = P - B\),判斷 \(\vec a\times \vec b\) 的正負。

極座標系

不同於普通的二維直角座標系,我們用角度 \(\theta\) (極角)和離原點(極點)的距離 \(r\) (極距)描述一個空間中的位置 \((r,\theta)\) 。轉換到普通的直角座標系,就變成了 \((r\cos \theta, r\sin \theta)\)

極座標系描述一些圖形的時候比較便利,比如對於單位圓 \(C: x^2+y^2=1\) ,變成極座標方程就是 \(r(\theta) = 1\)

為了將平面直角座標系的資訊轉化為極座標系的資訊,我們需要使用特殊的三角函式:\(\mathrm{atan2}\),這個函式已經內置於 \(\rm C++\)cmath 中。

\(\theta = \mathrm{atan2}(y,x), r = \frac{x}{\cos \theta}\)

基本操作

繞某點旋轉

給定兩個點 \(P_1, P_2\) 和一個旋轉角度 \(\theta\),求 \(P_1\)\(P_2\) 順時針旋轉 \(\theta\) 後得到的點 \(P_3\)

這裡直接給出公式吧:

\[\begin{aligned} x_{P_3}& = \left(x_{P_1} - x_{P_2}\right)\cos \theta - \left(y_{P_1} - y_{P_2}\right)\sin \theta + x_{P_2}\\ y_{P_3}& = \left(x_{P_1} - x_{P_2}\right)\sin \theta + \left(y_{P_1} - y_{P_2}\right)\cos \theta + y_{P_2}\\ \end{aligned} \]

求直線交點

分別給定兩條直線 \(l_1, l_2\) 上的兩個點:\(A,B,C,D (A,B\in l_1, C,D\in l_2)\) ,求出交點 \(P (P = l_1\cap l_2)\)

稍微給個圖:

作圖說明:\(AE//BD, DE//AB,CF//AB,EF//CD, AE\cap DE = E, EF\cap CF = F\)

容易得到:

\[\begin{aligned} S_{\text{四邊形}ABDE} = \left|\overrightarrow{BA}\times \overrightarrow{BD}\right|\\ S_{\text{四邊形}CDEF} = \left|\overrightarrow{CD}\times \overrightarrow{CF}\right|\\ \end{aligned} \]

因為 \(_{\text{四邊形}ABDE},S_{\text{四邊形}CDEF}\) 共底,因此面積比等於高比,進而得到

\[\frac{\left|\overrightarrow{DP}\right|}{\left|\overrightarrow{DC}\right|} = \frac{S_{\text{四邊形}ABDE}}{S_{\text{四邊形}CDEF}} = \frac{\left|\overrightarrow{BA}\times \overrightarrow{BD}\right|}{\left|\overrightarrow{CD}\times \overrightarrow{CF}\right|} \]

因此可以得到:

\[\overrightarrow{DP} = \frac{\left|\overrightarrow{BA}\times \overrightarrow{BD}\right|}{\left|\overrightarrow{CD}\times \overrightarrow{CF}\right|}\times \overrightarrow{DC} \]

接著就能得到 \(P\) 的座標了:

\[P = D + \frac{\left|\overrightarrow{BA}\times \overrightarrow{BD}\right|}{\left|\overrightarrow{CD}\times \overrightarrow{CF}\right|}\times (D - C) \]

凸包

判斷點和凸多邊形的關係

運用叉積可以判斷一個點是否在凸包中。

具體而言,假設需要判斷一個點 \(P\) 是否在凸包中,可以逆時針掃描凸包,對於每一條邊 \(A_{i-1}A_i\) ,都需要滿足 \((P-A_{i-1})\times \overrightarrow{A_{i-1}A_i} \ge 0\) 。這個演算法顯然是 \(O(\text{凸包邊數})\) 的,需要進行一點優化。

Algorithm 1

做一條直線 \(y = y_P\) ,這條直線和凸包的交點為 \(P_1, P_2\)(當交點小於一個的時候,顯然 \(P\) 不在凸包中),我們可以直接判斷 \(x_P\)\(P_1, P_2\) 的大小關係,當且僅當 \(x_{P_1}\leq x_P\leq x_{P_2}\) 的時候,\(P\) 在凸包中。

演算法瓶頸顯然在找交點的部分,本人不太懂。

Algorithm 2

運用叉積,設凸多邊形按逆時針轉組成的點序列為 \(P_{1\dots m}\) ,那麼作 \(m-1\) 條射線,其中第 \(i\) 條射線 \(L_i\) 的的起點為 \(P_1\),方向為向量 \(\overrightarrow{P_1P_{i+1}}\) 的方向,這個操作實際上就是將多邊形三角剖分。

顯然,當 \(P\)\(L_1\) 右側或者 \(L_{m-1}\) 左側,那麼 \(P\) 不在凸包中。接著,可以用二分的方式找到點 \(P\) 被夾在哪兩條射線中,假設被夾在射線 \(L_i, L_{i+1}\) 之間,用叉積判斷一下這個點是否在 \(\overrightarrow{P_{i+1}P_{i+2}}\) 的左側(或者就在向量所在的線段上),如果是,那麼 \(P\) 在凸多邊形中,否則就不在。

bool jdg_in_hull(Vector2* hl/*凸多邊形按照逆時針的序列*/, int hl_sz, Vector2 p) {
    
    if (cross(p - hl[1], hl[hl_sz] - hl[1]) < 0 || cross(p - hl[1], hl[2] - hl[1]) > 0) return false;
    int l = 2, r = hl_sz - 1, bl = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (cross(hl[mid] - hl[1], p - hl[1]) > 0) bl = mid, l = mid + 1;
        else r = mid - 1;
    }
    return cross(p - hl[bl], hl[bl + 1] - hl[bl]) <= 0;
}

找凸包

\(\Rightarrow \rm luogu\) 的模版題

給定 \(n\) 個平面上的點,我們選擇找到其中的一些點,其圍成的多邊形可以將這 \(n\) 個點包起來(在凸包內/在凸包上)。

Algorithm 1: Andrew演算法

將所有點按照橫座標為第一關鍵字,縱座標為第二關鍵字從小到大排序,用一個棧維護凸殼,維護條件可以是滿足棧中相鄰的點的叉積都是正數,先順序掃一遍所有點找到下凸殼,然後從棧中最後一個點出發,按逆序再掃一遍點,得到上凸殼,然後就能得到整個凸包了。

struct Vector2 {
    LD x, y;
    Vector2(LD x = 0, LD y = 0) : x(x), y(y) {}
} p[maxn], stk[maxn << 1];
int stk_tp;

inline Vector2 operator + (Vector2 a, Vector2 b) { return Vector2(a.x + b.x, a.y + b.y); }
inline Vector2 operator - (Vector2 a, Vector2 b) { return Vector2(a.x - b.x, a.y - b.y); }
inline bool operator < (Vector2 a, Vector2 b) {
    return (a.x == b.x ? a.y < b.y : a.x < b.x);
}
inline LD len(Vector2 a) { return sqrt(a.x * a.x + a.y * a.y); }
inline LD cross(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; }

void find_hull() {
    sort(p + 1, p + 1 + n);
    stk[1] = p[1], stk[stk_tp = 2] = p[2];
    for (int i = 3; i <= n; i++) {
        while (stk_tp > 1 && cross(p[i] - stk[stk_tp - 1], stk[stk_tp] - stk[stk_tp - 1]) > 0)
            stk_tp--;
        stk[++stk_tp] = p[i];
    }
    stk[++stk_tp] = p[n - 1];
    for (int i = n - 2; i >= 1; i--) {
        while (stk_tp > 1 && cross(p[i] - stk[stk_tp - 1], stk[stk_tp] - stk[stk_tp - 1]) > 0)
            stk_tp--;
        stk[++stk_tp] = p[i];
    }
    //此時棧中的點就是凸包按逆時針轉的點,p[1]被計算兩次
}

Algorithm 2: Graham 掃描法

使用極座標系。

按橫座標為第一關鍵字,縱座標為第二關鍵字找到最小的點,然後以這個點為極點,然後將剩餘的點按照極角從小到大排序,依然用棧維護凸包,順序掃一遍就行了。

//這是大佬 klii(tjp) 寫的
using namespace std;
const int N = 2e5 + 5;
struct Vec {
    double x, y;    
}p[N];
int n, stk[N], top;
Vec getvec(int a, int b) {
    return (Vec){p[b].x - p[a].x, p[b].y - p[a].y};
}
double Cross(Vec a, Vec b) {
    return a.x * b.y - a.y * b.x;
}
double dist(int a, int b) {
    return sqrt((p[a].x - p[b].x) * (p[a].x - p[b].x) + (p[a].y - p[b].y) * (p[a].y - p[b].y));
}
bool cmp(Vec a, Vec b) {
    if (a.x == p[1].x && b.x == p[1].x) return a.y < b.y;
    double t1 = atan2(a.y - p[1].y, a.x - p[1].x);
    double t2 = atan2(b.y - p[1].y, b.x - p[1].x);
    return t1 == t2 ? a.x < b.x : t1 < t2;
}
int main() {
    scanf("%d", &n);
    int idx = 1;
    for (int i = 1; i <= n; i++) {
        scanf("%lf%lf", &p[i].x, &p[i].y);
        if (p[i].y < p[idx].y || p[i].y == p[idx].y && p[i].x < p[idx].x) idx = i;
    }
    swap(p[1], p[idx]);
    sort(p + 2, p + 1 + n, cmp);
    stk[top = 1] = 1;
    for (int i = 2; i <= n; i++) {
        while (top > 1 && Cross(getvec(stk[top - 1], stk[top]), getvec(stk[top], i)) < 0) top--;
        stk[++top] = i;
    }
    double ans = 0;
    stk[top + 1] = stk[1];
    for (int i = 1; i <= top; i++) ans += dist(stk[i], stk[i + 1]);
    printf("%.2lf", ans);
    return 0;
}

旋轉卡殼

讀音多樣的演算法,可以用於求出凸包的直徑(凸包中最遠兩點的距離)。

\(\Rightarrow \rm luogu\) 的模版題

可以想到一種簡單的暴力:首先列舉一條凸包上的邊,然後找到距離這條邊最遠的點,然後用這條邊的兩個端點和那個點連線,更新答案。求距離的方法可以是行列式求出面積,然後除去邊的長度。

而旋轉卡殼實際上就是將上面的暴力優化一下而已。考慮順時針考慮邊 \(l\) ,假設當前考慮了第 \(i\) 條邊,而此時最遠點為 \(p_j\) ,接下來考慮第 \(i + 1\) 條邊,最遠點顯然會是 \(p_j\) 順時針移動一定次數到達的點,而不會逆時針移動,這顯然不優。

考慮順時針移動的總次數,可以發現每個凸包上的點只會被經過 \(O(1)\) 次,因此總的時間複雜度顯然是 \(O(n)\) 的。

//PS:早期碼風
int operator * (Vector2 a, Vector2 b) {
    return a.x * b.y - a.y * b.x;
}
LL dist(Vector2 a, Vector2 b) {
    Vector2 c = a - b;
    return c.x * c.x + c.y * c.y;
}
void find_hull() {
    sort(a + 1, a + 1 + n);
    stk[tp = 1] = a[1];
    for (int i = 2; i <= n; i++) {
        while (tp > 1 && (stk[tp] - stk[tp - 1]) * (a[i] - stk[tp - 1]) <= 0) 
            tp--;
        stk[++tp] = a[i];
    }
    int tmp = tp;
    for (int i = n - 1; i >= 1; i--) {
        while (tp > tmp && (stk[tp] - stk[tp - 1]) * (a[i] - stk[tp - 1]) <= 0)
            tp--;
        stk[++tp] = a[i];
    }
    for (int i = 1; i <= tp; i++) p[i] = stk[i];
    p_sz = tp - 1;
}

Vector2 cc;
LL diame;

void calc_diame() {
    diame = 0;
    if (p_sz == 2) {
        cc = (p[1] + p[2]) / 2;
        diame = dist(p[1], p[2]);
        return ;
    } 
    int to = 2;
    for (int i = 1; i <= p_sz; i++) {
        //移動
        while ((p[i+1]-p[i])*(p[to]-p[i]) < (p[i+1]-p[i])*(p[to+1]-p[i]))
            to = to % p_sz + 1;
        //更新答案
        if (diame < dist(p[to], p[i])) {
            diame = dist(p[to], p[i]);
            cc = (p[to] + p[i]) / 2;
        } 
        if (diame < dist(p[to], p[i+1])) {
            diame = dist(p[to], p[i+1]);
            cc = (p[to] + p[i+1]) / 2;
        }
    }
}

例題: P3187 [HNOI2007]最小矩形覆蓋

\(\Rightarrow\) 連結

可以想到答案的矩形的其中一條邊一定和凸包上某一條邊共線。

因此可以用旋轉卡殼求出每一條邊 \(l\) 對應的最優矩形的高度,至於寬度,顯然是由一個和 \(l\) 組成的點積最小的點和一個 \(l\) 組成的點積最大的點確定,顯然,這兩個點也可以用類似旋轉卡殼的方式維護。

void calc_ans() {
    int j1 = 2, j2 = 2, j3 = 1; 
    stk_tp--;
    for (int i = 1; i <= stk_tp; i++) {
        while (cross(stk[j1 + 1] - stk[i], stk[i + 1] - stk[i]) >= cross(stk[j1] - stk[i], stk[i + 1] - stk[i]))
            j1 = j1 % stk_tp + 1;
        while (dot(stk[j2 + 1] - stk[i + 1], stk[i] - stk[i + 1]) <= dot(stk[j2] - stk[i + 1], stk[i] - stk[i + 1]))
            j2 = j2 % stk_tp + 1;
        if (i == 1) j3 = j1;
        while (dot(stk[j3 + 1] - stk[i], stk[i + 1] - stk[i]) <= dot(stk[j3] - stk[i], stk[i + 1] - stk[i]))
            j3 = j3 % stk_tp + 1;
        LD len_i = len(stk[i + 1] - stk[i]);
        LD  l = dot(stk[j2] - stk[i], stk[i + 1] - stk[i]) / len_i,
            r = dot(stk[j3] - stk[i + 1], stk[i] - stk[i + 1]) / len_i,
            u = cross(stk[j1] - stk[i], stk[i + 1] - stk[i]) / len_i;
        l = abs(l), r = abs(r), u = abs(u);
        LD S = (l + r - len_i) * u;
        if (S < ans) {
            ans = S;
            //PS : 這裡的點是順時針的,要倒著輸出
            ap1 = stk[i + 1] - (r / len_i) * (stk[i + 1] - stk[i]),
            ap2 = ap1 + ((l + r - len_i) / len_i) * (stk[i + 1] - stk[i]),
            ap3 = ap2 + (u / len(stk[j2] - ap2)) * (stk[j2] - ap2),
            ap4 = ap3 - ((l + r - len_i) / len_i) * (stk[i + 1] - stk[i]);
        }
    }
}

半平面交

半平面:給定一條在平面上的直線 \(l\) ,在直線上方/下方的所有點的點集被稱為半平面,一半定義形式為:

\[\begin{aligned} &\{(x,y) \mid Ax+By+C\leq 0\}\\ &\{(x,y) \mid [(x,y)-A]\times [B,A]\leq 0, A,B\in l\} \end{aligned} \]

這裡的 \(\leq\) 可以換成 \(<,>,\ge\)
兩種表示方式各有優勢,其中求多個凸多邊形的並相關資訊的時候常用第二種。

\(\Rightarrow \rm luogu\) 模版題

這裡設定所有半平面都是直線左側的(即不等號為 $\ge $)。

這裡先將所有直線都找出來,然後按照極角從小到大排序,這裡用極角代替斜率可以避免討論斜率不存在的情況。

接著用一個雙端佇列 \(\rm deque\) 維護資訊。

考慮加入一條新的直線 \(l\) ,其中隊尾的直線為 \(l_1\), 隊尾上一條直線是 \(l_2\) ,其交點為 \(P\) ,如果 \(P\)\(l\) 的右側,那麼加上 \(l\) 後半平面的交等價於加上 \(l\) ,然後刪除 \(l_1\) 的交,因此可以直接將 \(l_1\) 彈出(這裡直接用 \(\rm dalao~klii\) 的圖);如果 \(P\)\(l\) 的左側,那麼 \(l_1\) 無法捨去。(\(l\rightarrow \text{紅}, l_1\rightarrow \text{綠}, l_2\rightarrow \text{藍}\))

同時 \(l\) 還可能對隊首產生影響:(\(l\rightarrow \text{紅},\text{隊首}\rightarrow \text{綠},\text{隊首下一個}\rightarrow \text{藍}\))

因此當隊頭的交點在 \(l\) 的右側的時候還需要彈出隊頭。

struct Line {
    Vector2 a, b; LD k;
    Line(Vector2 a = Vector2(0, 0), Vector2 b = Vector2(0, 0), LD k = 0) : a(a), b(b), k(k) {}
} l[maxn], tl[maxn];
inline LD get_slope(Vector2 a) { return atan2(a.y, a.x); }
bool cmp(Line a, Line b) {
    return abs(a.k - b.k) > EPS ? a.k < b.k : cross(b.a - a.a, a.b - a.a) > EPS;
}
inline bool chk_right(Line a, Vector2 b) { return cross(b - a.a, a.b - a.a) > EPS; }
Vector2 its[maxn];
inline Vector2 get_intersect(Line a, Line b) {
    return a.a + (cross(b.a - a.a, b.b - b.a) / cross(a.b - a.a, b.b - b.a)) * (a.b - a.a);
}
int q[maxn], ql, qr;
LD calc_intersect_S() {
    sort(l + 1, l + 1 + m, cmp);
    ql = 1, qr = 0; int cnt;
    tl[cnt = 1] = l[1];
    for (int i = 2; i <= m; i++) if (abs(l[i].k - l[i - 1].k) > EPS)
        tl[++cnt] = l[i];
    for (int i = 1; i <= cnt; i++) {
        while (ql < qr && chk_right(tl[i], its[qr])) qr--;
        while (ql < qr && chk_right(tl[i], its[ql + 1])) ql++;
        q[++qr] = i;
        if (ql < qr) its[qr] = get_intersect(tl[q[qr - 1]], tl[q[qr]]);
    }
    while (ql < qr && chk_right(tl[q[ql]], its[qr])) qr--;
    its[ql] = get_intersect(tl[q[ql]], tl[q[qr]]);
    LD res = 0;
    for (int i = ql + 2; i <= qr; i++) res += cross(its[i - 1] - its[ql], its[i] - its[ql]);
    return res / 2;
}
Vector2 pls[maxn];
int main() {
    int n, mi; scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &mi);
        for (int j = 1; j <= mi; j++) scanf("%Lf %Lf", &pls[j].x, &pls[j].y);
        pls[mi + 1] = pls[1];
        for (int j = 1; j <= mi; j++)
            l[++m] = Line(pls[j], pls[j + 1], get_slope(pls[j + 1] - pls[j]));
    }
    printf("%.3Lf\n", calc_intersect_S());
    return 0;
}

給一道簡單例題:\(\Rightarrow\) P3222 [HNOI2012]射箭

閔可夫斯基和

給個定義:設 \(A,B\) 為兩個點集,其閔可夫斯基和為 \(A+B\) 。運算規則如下:

\[C=A+B=\{a+b \mid a\in A, b\in B\} \]

可以這麼理解:將 \(B\) 中每個點按照 \(A\) 中每個點偏移得到的點集。
對於任意兩個點集的閔可夫斯基和的時間複雜度貌似只能做到 \(O(|A||B|)\) 。但是如果是求閔可夫斯基和的凸包,則可以做到 \(O(|A\text{ 的凸包點數}|+|B\text{ 的凸包點數}|)\)

設點集 \(A\) 的凸包多邊形為 \(H(A)\)

首先,求出 \(H(A),H(B)\) 。分別選定 \(H(A),H(B)\) 中的座標最小(這裡選用橫座標最小情況下縱座標最小)的點 \(P_1, P_2\) ,那麼 \(H(A+B)\) 中座標最小的點顯然是 \(P_1+P_2\)

然後通過畫圖找規律可以發現,\(H(A+B)\) 中的所有邊都是在 \(H(A),H(B)\) 中出現過的,因此將 \(H(A),H(B)\) 中的邊逆時針組成一個序列,然後按照極角從小到大歸併,即可得到 \(H(A+B)\)

int hl1_sz, hl2_sz, shl_sz;
struct Vector2 { ... } p1[maxn], p2[maxn], hl1[maxn], hl2[maxn], l[maxn], shl[maxn];
...
bool hull_cmp(Vector2 a, Vector2 b) { return a.x != b.x ? a.x < b.x : a.y < b.y; }

Vector2 t[maxn], t2[maxn]; int tsz;
void get_hull(Vector2* p, int n, Vector2* hl, int& cnt) {
    sort(p + 1, p + 1 + n, hull_cmp), t[1] = p[1], t[tsz = 2] = p[2];
    for (int i = 3; i <= n; i++) {
        while (tsz > 1 && cross(p[i] - t[tsz - 1], t[tsz] - t[tsz - 1]) >= 0) tsz--;
        t[++tsz] = p[i];
    }
    t[++tsz] = p[n - 1];
    for (int i = n - 2; i >= 1; i--) {
        while (tsz > 1 && cross(p[i] - t[tsz - 1], t[tsz] - t[tsz - 1]) >= 0) tsz--;
        t[++tsz] = p[i];
    }
    cnt = tsz - 1;
    for (int i = 1; i <= tsz; i++) hl[i] = t[i];
}
//這裡可能會有一些重複邊或者點,需要對shl再做一次凸包才能得到 H(A+B) 
void Minkowski(Vector2* hl1, Vector2* hl2, int hl1_sz, int hl2_sz, Vector2* shl, int& shl_sz) {
    int p1 = 0, p2 = 0;
    shl[shl_sz = 1] = hl1[1] + hl2[1];
    for (int i = 1; i < hl1_sz; i++) t[i] = hl1[i + 1] - hl1[i];
    for (int i = 1; i < hl2_sz; i++) t2[i] = hl2[i + 1] - hl2[i];
    t[hl1_sz] = hl1[1] - hl1[hl1_sz], t2[hl2_sz] = hl2[1] - hl2[hl2_sz];
    while (p1 < hl1_sz || p2 < hl2_sz) {
        Vector2 cho;
        if (p1 == hl1_sz) cho = t2[++p2];
        else if (p2 == hl2_sz) cho = t[++p1];
        else cho = (cross(t[p1 + 1], t2[p2 + 1]) >= 0 ? t[++p1] : t2[++p2]);
        shl[shl_sz + 1] = cho + shl[shl_sz], ++shl_sz;
    }
}

Minkowski(hl1, hl2, hl1_sz, hl2_sz, shl, shl_sz);
get_hull(shl, shl_sz, hl1, hl1_sz);
// hl1[1...hl1_sz] 就是最終的凸包

例題

P4557 [JSOI2018]戰爭

\(\Rightarrow \rm luogu\) 連結

給個簡介的題意:給定兩個點集 \(A,B\) ,進行 \(Q\) 次詢問,每次詢問給定一個偏移向量 \((dx, dy)\) ,將 \(B\) 中所有的向量按這個偏移向量偏移,問此時 \(A,B\) 的凸包是否存在交集。

可以想到,存在交集當且僅當 \(\exists a\in A\text{的凸包}, b\in B\text{的凸包}, b+(dx,dy) = a\),將其變形,可以得到:

\[\exists a\in A\text{的凸包}, b\in B\text{的凸包}, (dx,dy) = a-b \]

轉化一下,可以得到這個命題:

\[(dx,dy) \in (A-B)\text{的凸包} \]

這裡的 \(-B\) 是指將 \(B\) 中的所有座標取反。

這裡直接用閔可夫斯基和求出 \(A-B\) 的凸包,然後判斷一下 \((dx,dy)\) 是否在凸包中即可。