Loading

李超线段树

基本概念

李超线段树是由学军中学队爷李超提出的一种数据结构,是线段树的一个变种。李超线段树可以维护函数定点最值,它通常用来处理这种类型的问题:每次可以在平面上加入一条线段,试求与直线 \(x = k\) 相交的的线段中,交点纵坐标最大(小)的线段编号。

李超线段树不需要更新结点信息和下传标记,它的单次修改时间复杂度为 \(O(log^2n)\),单次查询时间复杂度为 \(O(logn)\) 。由于笔者不擅长数学,因此本文默认读者已经掌握了基本的几何只是。

算法思想

李超线段树维护 \(x\) 轴区间所对应的 最优势线段。最优势线段的定义为在区间中点处的最高线段,或者是暴露在区间最高折线中且横坐标跨度最大的线段。我们维护区间的最优势线段,每次查询位置 \((x, +\infty)\) 时直接取所有包含该位置的区间最优势线段的最大值即可。

最优势线段

如上图,区间 \([L, R]\) 的最优势线段为红色线段。

假设某区间 \([L, R]\) 原本的最优势线段为 \(l_1\),现在要加入的线段为 \(l_2\)。考虑维护该区间新的最优势线段,分类讨论:

  1. \(l_1\) 的两端点均比 \(l_2\) 对应的端点高,直接取 \(l_1\) 作为区间最优势线段。

episode_1

  1. \(l_1\) 的两端点均比 \(l_2\) 对应的端点低,更新区间 \([L, R]\) 的最优势线段为 \(l_2\)

episode_2

  1. \(l_1\)\(l_2\) 有交点,分类讨论:

    • 如果 \(l_2\) 与区间左端点的交点高于 \(l_1\) 与区间左端点的交点,此时若 \(l_1\)\(l_2\) 的交点在区间左半侧,说明线段 \(l_2\) 可能无法成为区间右半侧的最优势线段。因此不更新区间 \([L, R]\) 的最优势线段,用线段 \(l_2\) 递归维护左半区间的最优势线段。

    episode_3

    • 反之,说明 \(l_2\) 作为区间最优势线段比 \(l_1\) 更优,此时区间左半侧的最优势线段为 \(l_2\),用 \(l_1\) 递归维护右半区间的最优势线段。

    episode_4

    • 如果 \(l_2\) 与区间右端点的交点高于 \(l_1\) 与区间右端点的交点,此时若 \(l_1\)\(l_2\) 的交点在区间右半侧,说明线段 \(l_2\) 可能无法成为区间左半侧的最优势线段。因此不更新区间 \([L, R]\) 的最优势线段,用线段 \(l_2\) 递归更新右半区间的最优势线段。

    episode_5

    • 反之,说明 \(l_2\) 作为区间最优势线段比 \(l_1\) 更优,此时区间右半侧的最优势线段为 \(l_2\),用 \(l_1\) 递归维护左半区间的最优势线段。

    episode_6

接着我们考虑一些具体的代码细节。首先,我们需要利用两点式和斜截式,存储输入直线的斜率。每次更新时利用斜率来求出两线段之间的交点以及线段在 \(x\) 处的 \(y\) 坐标。因为每次更新我们都会将区间折半维护,因此如果遇到了叶子结点,我们需要特判递归边界。因为每次加入线段最多会有 \(logn\) 个区间的贡献变化,而每次贡献变化修改的区间不超过 \(logn\) 个,所以单词修改时间复杂度为 \(O(log^2n)\),我们要相信李超线段树的常数真的很小,因为它还能套树链剖分过掉 \(1\)\(10\) 万……详见代码。

参考代码

例题链接

#include <cstdio>
#include <utility>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 5;
const int modx = 39989;
const int mody = 1e9;

struct node
{
	int l, r, cur;
	bool flag;
} tree[maxn << 2];

int n, lcnt;
pair<double, double> line[maxn];

double f(pair<double, double> l, int x)
{
	return l.first * x + l.second;
}

double inter(pair<double, double> x, pair<double, double> y)
{
	return (y.second - x.second) / (x.first - y.first);
}

int get_max(int x, int a, int b)
{
	if (f(line[a], x) > f(line[b], x))
		return a;
	return b;
}

void build(int k, int l, int r)
{
	tree[k].l = l;
	tree[k].r = r;
	if (l == r)
		return;
	int mid = (l + r) / 2;
	build(2 * k, l, mid);
	build(2 * k + 1, mid + 1, r);
}

void update(int k, int x, int l, int r)
{
	if (tree[k].r < l || tree[k].l > r)
		return;
	int mid = (tree[k].l + tree[k].r) / 2;
	if (tree[k].l >= l && tree[k].r <= r)
	{
		if (!tree[k].flag)
		{
			tree[k].cur = x;
			tree[k].flag = true;
			return;
		}
		double ly = f(line[tree[k].cur], l), ry = f(line[tree[k].cur], r);
		double ly1 = f(line[x], l), ry1 = f(line[x], r);
		if (ly1 <= ly && ry1 <= ry)
			return;
		if (ly1 >= ly && ry1 >= ry)
		{
			tree[k].cur = x;
			return;
		}
		double point = inter(line[tree[k].cur], line[x]);
		if (ly1 >= ly)
		{
			if (point <= mid)
				update(2 * k, x, l, r);
			else
			{
				update(2 * k + 1, tree[k].cur, l, r);
				tree[k].cur = x;
			}
		}
		else
		{
			if (point > mid)
				update(2 * k + 1, x, l, r);
			else
			{
				update(2 * k, tree[k].cur, l, r);
				tree[k].cur = x;
			}
		}
		return;
	}
	if (l <= mid)
		update(2 * k, x, l, r);
	if (r > mid)
		update(2 * k + 1, x, l, r);
}

int query(int k, int x)
{
	int res = 0;
	if (tree[k].flag)
		res = get_max(x, res, tree[k].cur);
	if (tree[k].l == tree[k].r)
		return res;
	int mid = (tree[k].l + tree[k].r) / 2;
	if (x <= mid)
		res = get_max(x, res, query(2 * k, x));
	else
		res = get_max(x, res, query(2 * k + 1, x));
	return res;	
}

int main()
{
	int opt, ans = 0;
	int xa, ya, xb, yb, k;
	double val;
	scanf("%d", &n);
	build(1, 1, 40000);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &opt);
		if (opt)
		{
			scanf("%d%d%d%d", &xa, &ya, &xb, &yb);
			xa = (xa + ans - 1) % modx + 1;
			ya = (ya + ans - 1) % mody + 1;
			xb = (xb + ans - 1) % modx + 1;
			yb = (yb + ans - 1) % mody + 1;
			if (xa ^ xb)
			{
				val = double(yb - ya) / (xb - xa);
				line[++lcnt] = make_pair(val, -val * xa + ya);
			}
			else
				line[++lcnt] = make_pair(.0, (double)max(ya, yb));
			update(1, lcnt, min(xa, xb), max(xa, xb));
		}
		else
		{
			scanf("%d", &k);
			k = (k + ans - 1) % modx + 1;
			ans = query(1, k);
			printf("%d\n", ans);
		}
	}
	return 0;
}
posted @ 2021-07-24 23:49  kymru  阅读(461)  评论(0编辑  收藏  举报