凸包学习笔记
凸包学习笔记
内容好多啊。
概念
常见场景
维护凸包
题目明确提出需要你维护一个凸包的信息,例如周长、面积,此时可以使用凸包直接维护。
最优解问题
相信大家对这个场景其实并不陌生,因为单调队列优化
需要注意的是,如果
满足单调的性质,那么当前不优的点之后一定不优,于是可以使用单调队列省掉 ,做到 的复杂度,这也是单调队列优化 的做法。也可以利用这一点来进行复杂度优化。
常见用法
普通场景
这种场景通常表现为所有点可以在构建凸包前预先知道并排序,或保证在插入凸包时元素有序。在这种情况下,我们可以直接插入单调栈的最后方从而简单维护。
#define il inline
#define pii pair<int,int>
#define fi first
#define se second
const int N=1e5+5;
pii operator -(pii x,pii y){return {x.fi-y.fi,x.se-y.se};}
int operator *(pii x,pii y){return x.fi*y.se-x.se*y.fi;}//向量叉积,值 >0 时前者的斜率小于后者
int top;
pii stk[N];
il void Insert(pii x){//将当前点插入上凸壳
while(top>=2&&(x-stk[top-1])*(x-stk[top])>=0)top--;
stk[++top]=x;
return ;
}
动态插入
这种场景通常表现为插入的点不一定有序,并且无法预先排序。在这种情况下,我们只能在凸包的中间插入点,并且通过插入点的状态进行动态维护。更具体的,对于当前点 set
或平衡树实现,复杂度会因为数据结构自身多带一个
set<pii>con;
il void Insert(pii x){
auto p1=con.lower_bound(x),p2=p1--;
pii a=*p1,b=*p2;
if((b-x)*(x-a)<=0)return ;
auto tmp=p2++;
while(true){
if(p2==con.end())break;
a=*tmp,b=*p2;
if((b-a)*(a-x)>0)break;
p2=con.erase(tmp);
tmp=p2++;
}
tmp=p1--;
while(true){
if(tmp==con.begin())break;
a=*p1,b=*tmp;
if((x-b)*(b-a)>0)break;
p1=con.erase(tmp),p1--;
tmp=p1--;
}
con.insert(x);
return ;
}
区间凸包
这种场景通常表现为需要编号在
struct SegTree{
#define lid (id<<1)
#define rid ((id<<1)|1)
int tot;
vector<pii>hul[N<<2];
void Insert(int id,int l,int r,int p,pll x){
int tmp=(int)hul[id].size();
while(tmp>=2&&(x-hul[id][tmp-2])*(x-hul[id][tmp-1])<=0)hul[id].pop_back(),tmp--;
hul[id].push_back(x);
//注意这里对凸包的维护受插入特点的影响,如果是动态插入的场景,需要利用树套树
if(l==r)return ;
int mid=(l+r)>>1;
if(p<=mid)Insert(lid,l,mid,p,x);
else Insert(rid,mid+1,r,p,x);
return ;
}
};
动态修改
这种场景通常表现为操作会对凸包中的点的位置造成影响。考虑修改对凸包的影响是不可估量的,于是我们只能采用暴力重构的方法,查询可以正常二分。考虑分块,若块长为
il void Update(int id){
int top=0;
for(int i=st[id];i<=en[id];i++){
while(top>=2&&(nod[i]-stk[id][top-1])*(nod[i]-stk[id][top])>=0)top--;
stk[id][++top]=nod[i];
//注意这里对凸包的维护受插入特点的影响,如果是动态插入的场景,需要利用平衡树
}
return ;
}
il void Modify(int p,pii x){
int P=pos[p];
nod[p]=x;
Update(P);
return ;
}
动态删除
这种场景通常表现为会删除某一些点。这种场景的做法非常之多,需要结合题意分析。
正难则反
对于离线并且只有删除操作的题目,显然我们可以转换操作顺序,将删除看作增加,于是可以在正常的复杂度内简单求解。
分块
显然删除也可以利用暴力重构思想,直接利用分块可以做到
线段树分治
考虑在一些题目中,可以将同一个点的增加和删除看作存在性的改变,对于每一个询问都存在一些点存在。考虑维护每一个点存在的时间
洋葱算法
考虑贪心。如果题目只删除一个点,当这个点不在凸包上时,我们发现凸包不变;当这个点在凸包上时,我们可以将这个点与两边点的连边去掉,设左边点的横坐标为
因为一层一层的凸包看着很像洋葱(?所以我叫它洋葱算法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律