一、例題引入
題意:
主人公小智一共會捕捉 只寶可夢,寶可夢有兩個屬性,攻擊值 ,防禦值 。每當捕捉到一隻新的寶可夢 ,小智有兩種方法判斷這隻寶可夢是否是無用的。
- 存在一隻寶可夢 ,使得 並且
- 存在兩隻寶可夢 ,使得 並且 ,
每當捕捉到一隻新寶可夢,需要輸出當前共有幾隻無用寶可夢。
題目來源:Gym 102302-I
思路:
一開始一直在推導式子,妄圖想要用一個指標同時維護這兩個變量,然後就自閉了…
後來在隊友提醒之下,發現這是一個線段參數方程。線段兩端點爲 ,則線段上的點可以如下表示。
可能不夠直觀,但我們可以通過斜率來證明這是線段的參數方程。
發現這是一個線段參數方程之後,此題就轉化成了一個動態維護凸殼的問題,凸殼如下所示。
觀察這個凸殼,我們定義比較函數如下。
struct Node {
int x,y;
bool operator < (Node T) const {
return x == T.x ? y > T.y : x < T.x;
}
}
然後每次增加一個點的時候,向兩邊進行擴展刪點即可。刪點的標準爲左右兩端點連線是否能夠覆蓋自己,如果能則刪,不能則保留。具體過程見下文代碼,代碼很短,簡單易懂。
例題總結:
此題最重要的兩個關鍵點。
- 識別線段的參數方程
- 根據題意構建凸殼模型,並用 SET 求解
代碼:
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
using namespace std;
struct Node{
ll x,y;
Node operator - (Node p) const {return {x-p.x,y-p.y};}
ll operator ^ (Node p) const {return x*p.y-y*p.x;}
bool operator < (Node p) const {return (x == p.x ? y > p.y : x < p.x);}
};
struct Hull : public multiset<Node>{
bool inside(iterator p){
auto t2 = next(p);
if(t2 == end()) return 0;
if(p == begin()) return t2->x > p->x && t2->y > p->y;
auto t1 = prev(p);
if(((*t1-*p)^(*t2-*p)) < 0) return 1;
else return 0;
}
void ins(Node p){
auto t = insert(p);
if(inside(t)) { erase(t); return; }
while(t != begin() && inside(prev(t))) erase(prev(t));
while(next(t) != end() && inside(next(t))) erase(next(t));
}
};
int main()
{
int n; scanf("%d",&n);
Hull hull;
rep(i,1,n){
ll x,y; scanf("%lld%lld",&x,&y);
hull.ins({x,y});
printf("%d\n",i-(int)hull.size());
}
return 0;
}
二、各類型凸殼總結
凸殼處理過程中,主要注意點在於左右兩邊的垂直,在接下來所舉的例子中,應仔細觀察對於垂直的處理。
先按 升序,再按 升序
每次插入新節點,只需要插入 set 並找到對應位置,然後依次判斷兩邊的節點是否應該被刪除。(代碼部分同例題)
- 上凸殼(可包含左邊的垂直線段
- 下凸殼(可包含右邊的垂直線段
先按 升序,再按 降序
- 上凸殼(可包含右邊的垂直線段
- 下凸殼(可包含左邊的垂直線段
特殊凸殼
此類凸殼兩邊都有垂直線段,因此我們需要重新思考左邊垂直線段的問題。我們以左邊點是否固定來進行區分。
- 左邊點固定
- 如果左邊點固定,我們則可以按照先按 升序,如果 , 升序,否則 降序
- 左邊點不固定
- 如果左邊點不固定,我們可以考慮使用兩個 SET 來進行維護,其中一個用於維護左邊界垂直線,實現起來細節較繁瑣
三、凸包維護
相較於種類繁多的凸殼,凸包維護較爲清晰。
- 按照 “ 升序 升序” 方法進行排序
- 起點爲最小值處,終點爲最大值處
- 上凸殼維護上半部分,下凸殼維護下半部分
- 用兩個 分別維護上下凸殼
- 上下凸殼判斷一個點是否刪除的代碼不同,但均使用叉乘
- 判斷點 是否該刪去,左邊點爲 ,右邊點爲
- 上凸殼 ^ 則刪除點
- 下凸殼 ^ 則刪除點
後記
維護 “凸殼” / “凸包” 的內容到此就結束了,最後補充幾點:
- 除了 ,也可以直接手寫 等平衡樹進行維護
- 凸包維護過程也可以尋找一個定點,根據極角序進行排序
最後祝大家 愉快,一起愛上凸包把!(๑•̀ㅂ•́)و✧
的旅行雖然充滿荊棘但一擡頭便能看見無數束光,請務必堅持下去,負重前行終有云開霧散之日!💪💪💪