[bzoj 2300] [HAOI2011]防线修建

给一个点集, 除了(0,0)和(n,0), 其他点的横纵坐标均是[1,n-1]内的整数. q个操作: - 询问上凸壳的长度, 精确到小数点后2位. - 删除一个点.

保证(0,0), (n,0)和(x,y)不被删除, 且没有重点. (点数≤10^5+3, q≤2*10^5, n>1) 为了写斜率优化, 先练一练动态加点的凸壳.

离线, 倒过来处理, 变删除为加入.

不像斜率优化里需要对斜率二分查找, 本题只需要维护凸壳, 顺便维护长度, 因此可以直接用stl::set. 然而出于练习的目的码了一发Splay. CDQ说她的Splay有6KB, 现在我信了......

我对每个点记录: 坐标, 与前驱, 后继的距离和连线的斜率. Splay需要支持插入, 查询前驱, 后继. 没写特定的删除函数, splay后直接修改指针.

加点的时候先按横坐标插入. 如果该点在原凸包内, 删除之. 否则, 向左右两边做Graham-scan式的更新.

Splay里的点排序, 应以横坐标为第一关键字, 纵坐标为第二关键字, 正如水平序Graham算法所做的那样.

const int N = 1e5 + 3, MAX_Q = 2e5;
const double Zero = 0, inf = 1.0/Zero;

template<typename T>
inline T square(const T& x)
{
	return x * x;
}

struct Point {
	int x, y;
	Point(int x=-1, int y=-1): x(x), y(y) {}
	Point operator-(const Point& o) const
	{
		return Point(x-o.x, y-o.y);
	}
	bool operator<(const Point& o) const
	{
		return x < o.x || (x == o.x && y < o.y);
	}
	friend int cross(const Point& a, const Point& b)
	{
		return a.x * b.y - a.y * b.x;
	}
	friend double dist(const Point& a, const Point& b)
	{
		return sqrt(square(a.x - b.x) + square(a.y - b.y));
	}
	friend double slope(const Point& a, const Point& b)
	{
		return (double)(a.y - b.y) / (a.x - b.x);
	}
} P[N];

namespace Splay {
	struct Node {
		Point p;
		double k[2], l[2];
		int t;
		Node* fa, * ch[2];

		Node(int=-1, int=-1);
		void* operator new(size_t);
		void setf(Node*, int);
	} *root, nodes[N + 1], * nil = nodes;

	Node::Node(int x, int y): p(x,y), t(-1), fa(0)
	{
		ch[0] = ch[1] = nil;
	}

	void* Node::operator new(size_t)
	{
		static Node* ptr = nodes + 1;
		return ptr++;
	}

	inline void Node::setf(Node* f, int t1)
	{
		t = t1;
		fa = f;
		t >= 0 ? f->ch[t] = this : root = this;
	}

	inline void rot(Node* y)
	{
		int t = y->t;
		Node* x = y->fa;
		y->setf(x->fa, x->t);
		y->ch[t^1]->setf(x, t);
		x->setf(y, t^1);
	}

	void splay(Node* x, Node* r=0)
	{
		Node* y;
		while ((y = x->fa) != r) {
			if (y->fa != r)
				rot(y->t ^ x->t ? x : y);
			rot(x);
		}
	}

	Node* walk(Node* x, int d)
	{
		Node* y = x->ch[d];
		while (y->ch[d^1] != nil) y = y->ch[d^1];
		if (y != nil) splay(y, x);
		return y;
	}

	void insert(Node* x)
	{
		Node* y = root;
		while (true) {
			if (y->p < x->p) {
				if (y->ch[1] == nil) {
					x->setf(y, 1);
					break;
				} else y = y->ch[1];
			} else {
				if (y->ch[0] == nil) {
					x->setf(y, 0);
					break;
				} else y = y->ch[0];
			}
		}
		splay(x);
	}
}

namespace Convex_Hull {
	using namespace Splay;
	
	double length;

	void init(int n)
	{
		root = new Node(0, 0);
		Node* t = new Node(n, 0);
		root->k[0] = inf, root->k[1] = 0;
		t->k[0] = 0, t->k[1] = -inf;
		length = root->l[1] = t->l[0] = n;
		t->setf(root, 1);
	}
	
	void process(Node* a, Node* b, int d)
	{
		while (b->k[d] == (a->k[d] = slope(a->p, b->p)) || ((a->k[d] < b->k[d]) == d)) {
			length -= b->l[d];
			Node* t = walk(b, d);
			t->setf(a, d);
			b = t;
		}
		length += a->l[d] = b->l[d^1] = dist(a->p, b->p);
		b->k[d^1] = a->k[d];
	}

	void add(int x, int y)
	{
		Node* a = new Node(x, y);
		insert(a);
		Node* l = walk(a, 0), * r = walk(a, 1);
		if (cross(a->p - l->p, r->p - l->p) >= 0) {
			l->setf(0, -1);
			r->setf(l, 1);
			return;
		}

		length -= l->l[1];
	
		process(a, l, 0);
		process(a, r, 1);
	}
}

pair<int, int> Q[MAX_Q];
bool cancel[N];
double ans[MAX_Q];

int main()
{
	using namespace Convex_Hull;
	int n, x, y, m, q;
	scanf("%d%d%d%d", &n, &x, &y, &m);
	init(n);
	add(x, y);
	Rep (i, 0, m) scanf("%d%d", &P[i].x, &P[i].y);
	scanf("%d", &q);
	Rep (i, 0, q) {
		scanf("%d", &Q[i].first);
		if (Q[i].first == 1) {
			scanf("%d", &Q[i].second);
			cancel[--Q[i].second] = true;
		}
	}
	Rep (i, 0, m)
		if (!cancel[i]) add(P[i].x, P[i].y);
	Down (i, q-1, 0) {
		if (Q[i].first == 2)
			ans[i] = length;
		else {
			int t = Q[i].second;
			add(P[t].x, P[t].y);
		}
	}
	Rep (i, 0, q)
		if (ans[i])
			printf("%.2f\n", ans[i]);
	return 0;
}