首页 > 其他分享 >数据结构——李超线段树 学习笔记

数据结构——李超线段树 学习笔记

时间:2024-07-21 20:40:27浏览次数:7  
标签:数据结构 return int double 线段 李超 mid calc

数据结构——李超线段树 学习笔记

维护直线

考虑线段树维护区间最优线段。

  • 其中,最优线段指的是,在区间 \([l,r]\) 中,中点 \(mid\) 处最优的线段。

  • 我们称一个线段在单点更优 / 最优,显然,是指此处的函数值更大。

  • 我们下面称一个线段在区间内更优 / 最优,是指在中点处的比较。

  • 我们称一个线段在区间 / 单点严格更优,是指在该区间任何一处都更优。

插入直线

首先考虑在区间 \([l,r]\) 中插入线段:

  • 若该区间无线段,那么直接让他成为最优线段。

  • 如果已经有了,注意到我们不方便将一个区间下传,因此标记永久化。

设新线段为 \(f\),当前的最优线段为 \(g\),考虑合并,

  • 我们钦定 \(f\) 在区间 \([l,r]\) 弱于 \(g\),如果不满足那么交换即可。
  1. 若在左右端点 \(f\) 都更弱,那么 \(g\) 严格优于 \(f\),\(f\) 不可能成为答案,不需要下传。

  2. 若在左端点 \(f\) 更优,因为 \(f\) 在中点更弱,因此左侧一定存在分界点,递归左侧。

  3. 若在右端点 \(f\) 更优,因为 \(f\) 在中点更弱,因此右侧一定存在分界点,递归右侧。

复杂度分析:

  • 因为两直线最多只有一个交点,因此左右最多递归一个。

  • 因此,时间复杂度为,单次 \(\mathcal O(n\log n)\)。


为何不能钦定 \(f\) 强于 \(g\) 然后加入 \(f\) 捏?

注意到如果 \(f\) 严格强于 \(g\),那么为了更新答案,我们还是需要交换 \(f,g\)。

然后这样这个问题相当于没有。

查询最值

标记永久化之后,我们需要把从根到叶子节点的每一个最优线段计算。

注意到是区最值,是可以重复的,那么我们随便搞就可以了。

时间复杂度同线段树,为单次 \(\mathcal O(\log n)\)。

代码

P4254 [JSOI2008] Blue Mary 开公司

struct line {
	double k, b;
} p[M];

int tot;

double calc(int u, int t) {
	return p[u].b + p[u].k * t;
}

#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)

int best[N << 2];

void modify(int k, int l, int r, int u) {
	int &v = best[k];
	int mid = (l + r) >> 1;
	if (calc(u, mid) > calc(v, mid)) swap(u, v);
	if (calc(u, l) > calc(v, l)) modify(ls(k), l, mid, u);
	if (calc(u, r) > calc(v, r)) modify(rs(k), mid + 1, r, u);
}

double query(int k, int l, int r, int t) {
	double res = calc(best[k], t);
	if (l == r) return res;
	int mid = (l + r) >> 1;
	if (t <= mid) res = max(res, query(ls(k), l, mid, t));
	else res = max(res, query(rs(k), mid + 1, r, t));
	return res;
}

void Insert(double k, double b) {
	p[++tot] = {k, b};
	modify(1, 1, (int)5e4, tot);
}

double Query(int t) {
	return query(1, 1, (int)5e4, t);
}

维护线段

插入线段

我们延续上面的思路,但是。

我们需要线段树式的遍历到每一个节点,才能更新最优线段。

  • 注意到线段树会把区间分为 \(\mathcal O(\log n)\) 个区间,

  • 我们需要对每个区间进行 \(\mathcal O(\log n)\) 的更新,

  • 因此,总时间复杂度是 \(\mathcal O(\log^2n)\) 的。

查询最值

和上面没有变化。

代码

下面是和上面类似的代码,也很好写。

struct line {
	double k, b;
} p[M];

int tot;

double calc(int u, int t) {
	return p[u].b + p[u].k * t;
}

#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)

int best[N << 2];

void update(int k, int l, int r, int u) {
	int &v = best[k];
	int mid = (l + r) >> 1;
	if (calc(u, mid) > calc(v, mid)) swap(u, v);
	if (calc(u, l) > calc(v, l)) update(ls(k), l, mid, u);
	if (calc(u, r) > calc(v, r)) update(rs(k), mid + 1, r, u);
}

void modify(int k, int l, int r, int p, int q, int u) {
	if (l >= p && r <= q) return update(k, l, r, u);
	int mid = (l + r) >> 1;
	if (q <= mid) return modify(ls(k), l, mid, p, q, u);
	if (p >= mid + 1) return modify(rs(k), mid + 1, r, p, q, u);
	modify(ls(k), l, mid, p, q, u), modify(rs(k), mid + 1, r, p, q, u);
}

double query(int k, int l, int r, int t) {
	double res = calc(best[k], t);
	if (l == r) return res;
	int mid = (l + r) >> 1;
	if (t <= mid) res = max(res, query(ls(k), l, mid, t));
	else res = max(res, query(rs(k), mid + 1, r, t));
	return res;
}

void Insert(double k, double b) {
	p[++tot] = {k, b};
	modify(1, 1, (int)5e4, tot);
}

double Query(int t) {
	return query(1, 1, (int)5e4, t);
}

P4097 【模板】李超线段树 / [HEOI2013] Segment

这个题目强制在线,且需要输出最优线段且编号最小,因此可能会被卡精度,

constexpr double eps = 1e-8;

inline int Cmp(double x, double y) {
	if (x - y > eps) return 1;
	if (y - x > eps) return -1;
	return 0;
}

inline pair<double, int> Max(const pair<double, int> &a,
							 const pair<double, int> &b) {
	int c = Cmp(a.first, b.first);
	if (c == 0) return a.second < b.second ? a : b;
	return c == 1 ? a : b;
}

标签:数据结构,return,int,double,线段,李超,mid,calc
From: https://www.cnblogs.com/RainPPR/p/18314925

相关文章

  • 算法设计与数据结构系列【超详细、超全面、小白可入,期末复习】持续更新中...
    算法设计与数据结构系列【超详细、超全面、小白可入,期末复习】持续更新中…24.07.21代码采用语言:Java1、位运算(BitwiseOperation)常见操作:与(&)、或(I)、非(~)、异或(^)移位运算:>>和<<分别为左移和右移>>>运算符:用0填充高位,>>用符号位填充高位,没有<<<运算符真值表ab~a~b......
  • 线段树分治
    线段树分治线段树分治可以解决这样的问题:对于一些操作,每个操作只在\(l\)~\(r\)的的时间内有效。对于一些操作,询问某个时间点所有操作的贡献。经典例题算法思路对于这道题,因为二分图的充要条件是不存在奇环,所以可以使用扩展域并查集解决。我们考虑离线优化:对于每......
  • 数据结构:栈的基本概念、顺序栈、共享栈以及链栈
    相关概念栈(Stack)是只允许在一端进行插入或删除操作的线性表。栈顶(Top):线性表允许插入删除的那一端。栈底(Bottom):固定的,不允许进行插入和删除的另一端。栈的基本操作InitStack(&S):初始化一个空栈S。StackEmpty(S):判断一个栈是否为空,若栈S为空则返回true,否则返回false。......
  • 线段树优化建图
    $\quad$在做题时,我们会遇到这种问题:区间性的连边。$\quad$显然,直接连边很容易\(T\)掉,而且内存占用也是我们无法接受的,所以我们就可以采用一种更加方便(其实看起来更麻烦)的方法--线段树优化建图。$\quad$首先我们要有一棵入树与出树(这里用一下_ducati的图)$\quad$入树......
  • 初阶数据结构的实现2 双向链表
    1.双向链表1.1概念与结构1.2实现双向链表1.2.1定义程序目标#define_CRT_SECURE_NO_WARNINGS1#pragmaonce#include<stdio.h>#include<assert.h>#include<stdlib.h>#include<stdbool.h>typedefintLTDateType;//定义双向链表结构typedefstructListNode{......
  • 很多logn级别的数据结构,为什么选择B+树?
    高效的范围查询:B+树的叶节点按顺序链接,可以很方便地进行范围查询。与B树不同,B+树的所有叶节点都包含在一个链表中,这使得范围查询和顺序访问非常高效。稳定的查找性能:B+树的所有叶节点在同一层,查找任何一个数据的路径长度都相同,保证了查找操作的时间复杂度为O(logn)。这意味......
  • 数据结构——栈
    一、栈的定义我们都知道线性表是具有相同数据类型的n(n为表长且n>=0)个数据元素的有限序列。而栈,是只允许在一端进行插入或删除操作的线性表。就如汉罗塔相似,你只能从头顶放入或拿走方块。  重要术语:栈顶、栈底、空栈我们从图就很容易理解这三个术语:空栈:指线性表内......
  • 数据结构:线性表-例题
    顺序存储结构和链式存储结构都可以进行顺序存取。[T/F]顺序存储结构可以进行顺序存取和随机存取;链式存储结构只可以进行顺序存取。散列存储结构能反应数据之间的逻辑关系。[T/F]散列存储通过散列函数映射到物理空间,不能反应数据之间的逻辑关系。链式存储设计时,结点......
  • 线段树优化建图
    首先看这个问题:一张\(N\)个点的有向图,初始没有任何边,有\(M\)次操作:建\(1\)条边\(u\rightarrowv\),边权为\(w\)。建\(r-l+1\)条边\(u\rightarrow\foralli\in[l,r]\),边权为\(w\)。建\(r-l+1\)条边\(\foralli\in[l,r]\rightarrowu\),边权为\(w\)。求建完边......
  • 【软考】数据结构与算法基础 - 数组和链表
    一、数组和链表的区别(很简单,但是很常考,记得要回答全面)什么是数组:C++语言中,可以用数组,处理一组数据类型相同的数据,不可以动态定义数组的大小(使用前,必须指定大小。)在实际应用中,用户使用数组之前,无法确定数组的大小只能够将数组定义成足够大小,多余出来空间可能不被使用,......