动态凸壳 学习笔记
djwj233
2021-11-08 21:23:21
## Q & A
和 myee 一起搞的(((
> ## 《蓝猫淘气三千问》
>
> #### 动态凸壳有什么用
>
> 可以动态地维护凸壳。
>
> #### 为什么要动态维护凸壳
>
> 好玩。
>
> #### 为什么动态维护凸壳好玩
>
> 可以用于斜率优化。
>
> #### 为什么要斜率优化
>
> 因为题目想让我们斜率优化。
>
> #### 为什么题目想让我们斜率优化
>
> 因为出题人毒瘤。
>
> #### 为什么出题人毒瘤
>
> 因为国民普遍素质不高。
>
> #### 为什么国民普遍素质不高
>
> 因为总有人在问为什么。
## 核心代码
记这个东西主要是因为今天看到了一种短小精悍的写法,借助了 `C++ STL multiset`(注意 `set` 和 `multiset` 还是有不少区别的,这里用 `multiset` 更方便)。
以下以维护右上凸壳为例。
### 点的表示
```c++
struct node {
db x, y;
node (db X = 0, db Y = 0) : x(X), y(Y) {}
node operator - (const node &temp) const {
return node(x-temp.x, y-temp.y);
}
db operator ^ (const node &temp) const { // 叉乘
return x * temp.y - y * temp.x;
}
bool operator < (const node &temp) const {
return x == temp.x ? y > temp.y : x < temp.x ;
}
} ;
```
### 凸壳
```c++
struct Convex_Hull {
multiset<node> S;
bool valid(multiset<node>::iterator it) {
auto it2 = next(it);
if(it == S.begin() || it2 == S.end()) return true;
auto it1 = prev(it);
return ((*it - *it1) ^ (*it2 - *it)) > 0.0; // *
}
void insert(node t) {
auto p = S.insert(t);
if(!valid(p)) { S.erase(p); return ; }
while(p != S.begin() && !valid(prev(p)))
S.erase(prev(p));
while(next(p) != S.end() && !valid(next(p)))
S.erase(next(p));
}
} H;
```
我们重点关注打星号的这个过程。
注意到,在右上凸壳中,如果一个点是凸的,那么一定长这样:
![](https://cdn.luogu.com.cn/upload/image_hosting/lok46y4o.png)
所以叉乘为正。
至于给定斜率的询问,我们可以考虑二分对应的 $x$ 坐标,在 `multiset` 中 `lower_bound` 处理。
~~复杂度好像退化成 2log 了?~~
### 维护直线
- [P4254 [JSOI2008]Blue Mary开公司](https://www.luogu.com.cn/problem/P4254)
看着就很李超树,但我不会李超树,考虑换种写法。
显然题目想让我们维护一个下凸壳,我们可以把叉积换成交点的 $x$ 坐标。
之后所有的二分都反着来,这题就做完了。
```c++
#include<bits/stdc++.h>
using namespace std;
#define fo(v,a,b) for(int v=a;v<=b;v++)
#define fr(v,a,b) for(int v=a;v>=b;v--)
#define cl(a,v) memset(a,v,sizeof(a))
typedef double db;
const db eps = 1e-8, inf = 1e9;
const int N = 100010;
int n;
inline bool eq(db x, db y) {
return fabs(x - y) <= eps;
}
inline bool leq(db x, db y) {
return x + eps < y;
}
struct Line {
db k, b;
Line(db K, db B) : k(K), b(B) {}
bool operator<(const Line &temp) const {
return eq(k, temp.k) ? leq(b, temp.b) : leq(k, temp.k);
}
db operator ^ (const Line &temp) const {
return (temp.b - b) / (k - temp.k);
}
} ;
struct Convex_Hull
{
multiset<Line> S;
bool valid(multiset<Line>::iterator it) {
auto it2 = next(it);
if(it2 == S.end()) return true;
if(it == S.begin()) return leq(it2->b + it2->k, it->b + it->k);
if(eq(it->k, it2->k)) return leq(it2->b, it->b);
auto it1 = prev(it);
if(eq(it->k, it1->k)) return leq(it1->b, it->b);
return leq((*it1) ^ (*it), (*it) ^ (*it2));
}
void insert(Line t) {
auto p = S.insert(t);
if(!valid(p)) return S.erase(p), void();
while(p != S.begin() && !valid(prev(p))) S.erase(prev(p));
while(next(p) != S.end() && !valid(next(p))) S.erase(next(p));
}
db check(db val) {
auto it = S.lower_bound(Line(val,-inf));
if(it == S.end()) return inf;
if(next(it) == S.end()) return inf;
return (*it) ^ (*next(it));
}
db query(int x) {
db l = 0.0, r = 100.0, mid;
while(l + 1e-6 < r) {
mid = (l + r) / 2.0;
if(leq((db)x, check(mid))) r = mid;
else l = mid;
}
auto it = S.lower_bound(Line(r,-inf));
return it->k * x + it->b;
}
} H;
int main()
{
cin >> n;
fo(i,1,n) {
char s[20]; scanf("%s", s+1);
if(s[1]=='P') {
db k, b; scanf("%lf%lf", &b, &k), b -= k;
H.insert(Line(k, b));
} else {
int x; scanf("%d", &x);
printf("%d\n", (int)max(0.0, H.query(x)/100.0) );
}
}
return 0;
}
```
这么说来,大部分李超树的题都可以用动态凸壳搞。