「学习笔记」李超线段树

「学习笔记」李超线段树

background

学这个算法的是因为某天一个题用$ ext{ set } $维护斜率被卡常数了,在某大佬的安利下学了这个科技,联赛后又思考了很多关于这个算法的问题,于是写一篇博客来颓废并调整一下文化课学习以来压抑的心态。

在平时的一些训练中往往遇到一些维护斜率的问题,根据 (x) 坐标单不单调和斜率单不单调可以大致分成几种,并用单调线性结构和 ( ext{ set , cdq})分治来解决,事实上李超线段树可以在没有删除操作的题目中成为较好的另外一种选择,其具有常数小和实现简单细节少等优点。另外还有一些动态维护的题目只能有李超树来实现,然而后面并不会讲到。

a problem 「Heoi2013」Segment

有一个二维平面,你需要支持两种操作,插入一条线段,查询一条直线 (x = k) 与其相交的最上面的一条线段。在学习之前离线做法可以用 ( ext{cdq}) 搞定,在线的话用平衡树也可以实现,李超树的做法相较于这两种要巧妙一些,核心的思想在于标记永久化维护覆盖区间中点的最高的线段。

考虑当前有一个线段树上的区间,要在这个区间里插入一条线段并维护答案,不妨分以下几类讨论:

  1. 如果当前这个区间还没有线段或者新加入的线段完全覆盖原来的线段,那么新加入的线段一定替换原来的线段
  2. 如果当前新加入的线段被原来的线段完全覆盖,那么这条新加入的线段一定不会再有用了。
  3. 将新加入的线段和原来的线段求交,将交所在的那半边较劣的线段下放到对应的儿子,更新区间中点的最高的线段。

此时维护的相当于是标记永久化的若干个线段,求答案就是将 (k) 对应路径上的标记取最优线段,复杂度瓶颈所在的第三种情况每次都只会走一个儿子下放,所以从插入一条线段的复杂度是 (O(logn)) ,由于要区间修改要在 (logn) 个节点上插入线段,所以总复杂度 (O(nlog^2n))

Why?

首先在没有任何线段的时候正确性显然,考虑逐步在一个正确的情况下插入线段,分析正确性是否改变。如果插入的线段是情况 (1, 2) ,那么正确性也是显然的,这里就不啰嗦了,直接考虑第 (3) 种情况。

考虑此时区间的线段是覆盖区间中点最高的线段,也就是所谓的优势线段。其有一个重要的性质就是任何一条线段在这个区间内与它相交,交不在的区间所有点都是新的比原先的优或者原先的比新的优,也就是交不在的区间的所有点的可能的答案还是优势线段。而考虑交所在的区间可能的答案有一部分是另外一条线段,此时只需要将不是优势线段的那一条(被替换的那一条)递归向下更新贡献即可,那么这个节点的形态就是一半边点就是对优势线段取 ( ext{max}),另外一半边对两者都取 ( ext{max}) 。(这里以求最上面的线段为例)。而每次插入都重新维护了优势线段,所以每次插入后的正确性还是对的。

Code

/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
	int ch = 0, f = 0; x = 0;
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
	for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
	if(f) x = -x;
}

const int N = 200005; 
const double eps = 1e-6;
#define lson (u << 1)
#define rson (u << 1 | 1)

int n, tot;
struct Seg{ double k, b; int id; };
struct SegmentTree{
	Seg s[N<<2]; int hav[N<<2];
	inline void pushdown(int u, int l, int r, Seg now){
		if(!hav[u]) return (void) (s[u] = now, hav[u] = 1);
		double l1 = now.k * l + now.b, r1 = now.k * r + now.b;
		double l2 = s[u].k * l + s[u].b, r2 = s[u].k * r + s[u].b;
		if(l2 >= l1 && r2 >= r1) return;
		if(l1 >= l2 && r1 >= r2) return (void) (s[u] = now);
		double pos = (now.b - s[u].b) / (s[u].k - now.k);
		int mid = l + r >> 1; 
		if(pos <= mid) pushdown(lson, l, mid, r1 > r2 ? s[u] : now);
		else pushdown(rson, mid + 1, r, l1 > l2 ? s[u] : now);
		if((l1 > l2 && pos >= mid) || (r1 > r2 && pos < mid)) s[u] = now;
	}
	inline void Insert(int u, int l, int r, int L, int R, Seg now){
		if(l >= L && r <= R) return (void) (pushdown(u, l, r, now));
		int mid = l + r >> 1;
		if(L <= mid) Insert(lson, l, mid, L, R, now);
		if(mid < R) Insert(rson, mid + 1, r, L, R, now);
	}
	inline Seg query(int u, int l, int r, int pos){
		if(l == r) return hav[u] ? s[u] : (Seg){0, 0, 0};
		int mid = l + r >> 1; Seg now;
		if(pos <= mid) now = query(lson, l, mid, pos);
		else now = query(rson, mid + 1, r, pos);
		if(!hav[u]) return now;
		double p1 = s[u].k * pos + s[u].b, p2 = now.k * pos + now.b;
		if(!now.id || (p1 > p2 || (fabs(p1-p2) < eps && s[u].id < now.id))) now = s[u];
		return now;
	}
}van;
signed main(){
	read(n); int lastans = 0;
	for(int i = 1, op, x, X0, Y0, X1, Y1; i <= n; i++){
		read(op);
		if(op == 1){
			read(X0), read(Y0), read(X1), read(Y1);
			X0 = (X0 + lastans - 1) % 39989 + 1;
			X1 = (X1 + lastans - 1) % 39989 + 1;
			Y0 = (Y0 + lastans - 1) % (int) (1e9) + 1;
			Y1 = (Y1 + lastans - 1) % (int) (1e9) + 1;
			if(X0 > X1) swap(X0, X1), swap(Y0, Y1);
			if(X0 == X1){
				van.Insert(1, 1, 39989, X0, X1, (Seg){0.0, max(Y0, Y1), ++tot});
				continue;
			}
			double k = (double)(Y1 - Y0) / (double)(X1 - X0), b = (double)Y1 - k * X1;
			van.Insert(1, 1, 39989, X0, X1, (Seg){k, b, ++tot});
		}
		else{
			read(x), x = (x + lastans - 1) % 39989 + 1;
			printf("%lld
", lastans = van.query(1, 1, 39989, x).id);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/mangoyang/p/9979465.html