[BZOJ2300][HAOI2011]防线修建(平衡树动态维护凸包)

发现正着操作几乎是不可做的。所以考虑退回到最后一次操作之后的状态,从后面倒着往前操作。这样删点就变成了加点。
首先把所有的点按照 x x 坐标排序,x坐标相同则只保留 y y 坐标最大者,然后求出剩下点集的上凸壳。
然后以x坐标为关键字,用Splay set维护上凸壳。
考虑加入一个点(下面设为 P P )的影响:
(1)如果一个点在凸包内,那么这个点对凸包不造成影响。判断P是否在凸包内的方法:在 set s e t 中找到 P P 的前驱X和后继 Y Y ,然后如果点P XY X Y 之下,那么这个点在凸包内,具体地:
P P 在凸包内PX×PY<0
(2)如果一个点在凸包外,这个点会加入凸包,并且可能会弹掉凸包上连续的几个点
P P 的左边有点X,如果 Y Y X的前驱,那么如果

PX×PY<0 P X → × P Y → < 0

X X 会被点P弹掉。
同样,如果 X X P的右边, Y Y X的后继,那么如果
PY×PX<0 P Y → × P X → < 0

X X 会被点P弹掉。
具体地,从 P P 开始向左右两边查找会被弹掉的点X
(3)同时,还要维护凸包的周长。
由于一个点最多被点 P P <script type="math/tex" id="MathJax-Element-4600">P</script>弹掉一次,所以复杂度是线性对数级别的。
代码:

#include <set>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read()
{
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 2e5 + 2018;
int tn = 3, tm, n, m, q, op[N], val[N], top, tot;
double ans[N], dis;
bool del[N];
struct cyx
{
    int x, y;
    cyx() {}
    cyx(int _x, int _y) :
        x(_x), y(_y) {}
    friend inline bool operator < (cyx a, cyx b)
    {
        return a.x < b.x;
    }
    friend inline cyx operator - (cyx a, cyx b)
    {
        return cyx(b.x - a.x, b.y - a.y);
    }
    friend inline int operator * (cyx a, cyx b)
    {
        return a.x * b.y - a.y * b.x;
    }
} a[N], b[N], c[N], stk[N];
set<cyx> s;
typedef set<cyx>::iterator it;
double dist(cyx a, cyx b)
{
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
bool comp(cyx a, cyx b)
{
    if (a.x != b.x) return a.x < b.x;
    return a.y > b.y;
}
void add_point(cyx p)
{
    it r = s.lower_bound(p), l = r, tmp; l--;
    if ((p - (*l)) * (p - (*r)) < 0) return;
    s.insert(p);
    dis -= dist(*l, *r);
    while (l != s.begin())
    {
        tmp = l; l--;
        if ((p - (*tmp)) * (p - (*l)) > 0) break;
        dis -= dist(*tmp, *l);
        s.erase(tmp);
    }
    while (r != s.end())
    {
        tmp = r; r++;
        if ((p - (*r)) * (p - (*tmp)) > 0) break;
        dis -= dist(*tmp, *r);
        s.erase(tmp);
    }
    s.insert(p); l = r = s.find(p); l--; r++;
    dis += dist(*l, p) + dist(*r, p);
}
int main()
{
    int i;
    b[1].x = b[1].y = b[2].y = 0; b[2].x = read();
    b[3].x = read(); b[3].y = read();
    m = read();
    For (i, 1, m) b[++tn].x = read(), b[tn].y = read();
    q = read();
    For (i, 1, q)
    {
        op[i] = read();
        if (op[i] == 1) del[val[i] = read() + 3] = 1;
    }
    For (i, 1, tn) if (!del[i])
        a[++n] = b[i];
    sort(a + 1, a + n + 1, comp);
    For (i, 1, n) if (i == 1 || a[i].x != a[i - 1].x)
        c[++tm] = a[i];
    stk[1] = c[1]; stk[top = 2] = c[2];
    For (i, 3, tm)
    {
        while (top > 1 &&
            (stk[top - 1] - stk[top]) * (stk[top - 1] - c[i]) >= 0)
                top--;
        stk[++top] = c[i];
    }
    For (i, 1, top)
    {
        s.insert(stk[i]);
        if (i < top) dis += dist(stk[i], stk[i + 1]);
    }
    Rof (i, q, 1)
        if (op[i] == 1) add_point(b[val[i]]);
        else ans[++tot] = dis;
    Rof(i, tot, 1)
        printf("%.2lf\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值