李超线段树

这个利用了线段树标记永久化的思想 , 支持查询很多条直线 \(y=kx+b\) (线段)在 \(x=k\) 的最值 .

常常可以在一些最优化问题中 优化时间复杂度 , 增强程序效率 .

算法简述

假设我们当前维护最大值 (最小值同理) .

用线段树维护每一个区间的一个 优势线段 (暴露在最上面的线段 , 也就是不会被别的线段在这个区间完全盖住)

对于网上那些说暴露最多的 , 他们程序都似乎不能体现 , 故个人理解是这样 .

可以证明 , 对于 \(x=k\) 在这些直线上的最大值 , 肯定是所有包含这个点的所有区间 优势线段 对应 \(y\) 的最大值 .

暴露在最上面线段 是个抽象化的过程 , 我们可以把这个看成在 \([l, r]\) 区间中 \(\displaystyle f(mid = \frac{l + r}{2})\) 的最大的那条直线 .

我们插入的时候讨论四种情况 qwq

  1. 这个区间本来没有线段 , 直接放在这里就行了 .
  2. 新的线段完全被旧的线段盖住 , 这种情况直接退出就行了 .
  3. 新的线段把旧的线段完全盖住 , 直接修改然后退出就行了 .
  4. 在这个区间中有交点 , 先改 , 然后把劣势的放入交点的那一侧 .

这个说起来容易 , 但实现起来有点细节 .

我们先比较 \(f(mid)\) 大小 , 如果新的大 , 那我们先交换 , 也就是说 把当前较为优势的先放在此处 , 劣势的拿下来等待安排 .

然后我们看新旧的交点 也就是 \(\displaystyle x= -\frac{b_1-b_2}{k_1-k_2}\) (注意 如果 \(k_1=k_2\) 要判断掉 , 直接退出就行了)

\(x\) 如果不在此段区间内 , 那么意味着优势的在这个区间都优于劣势 , 那么直接退出 , 否则 把当前劣势的下方入标点那侧继续递归处理 .

有时候判断交点不行 , 会错掉 , 其实最好的是判断斜率 .

因为有时候交点刚好在 \(mid\) 处时候 , 你可能会存在瞎走的情况 .

这时候斜率能帮助你判断接下来应该向哪里走 , 也就是它接下来哪里会最优 .

然后查询的话 , 在线段树上一直向下走 , 直到走入端点所处的区间 , 然后一路把存在的优势线段的 \(f(x)\)\(\max\) .

分析一波时间复杂度qwq ...

插入的时候每个线段最多被分成 \(O(\log n)\) 个区间 , 然后继续下放也需要 \(O(\log n)\) 的复杂度 , 插入的时候复杂度就是 \(O(\log^2 n)\) . (如果插入直线的话就是 \(O(\log n)\) 的复杂度)

然后查询的话和普通单点查询的复杂度是一样的 \(O(\log n)\) .

代码实现

const int N = 5e4 + 1e3;

struct Line {
	int l, r, id; double k, b;

	Line (int xl = 0, int xr = 0, int yl = 0, int yr = 0, int id = 0) {
		this -> id = id; l = xl, r = xr;
		if (xl != xr) k = 1.0 * (yr - yl) / (xr - xl), b = yl - k * xl;
		else k = .0, b = max(yl, yr);
	}

	double func(int x) { return k * x + b; }
} ;

const double eps = 1e-8;
inline int Sgn(double x) { return (x > eps) - (x < -eps); }

inline bool Cmp(Line a, Line b, int x) { 
	if (!a.id) return true;
	int dir = Sgn(a.func(x) - b.func(x)); 
	return (dir != 0) ? dir < 0 : a.id < b.id; 
}

#define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
struct Chao_Segment_Tree {
	Line Adv[N << 2];

	void Down(int o, int l, int r, Line up) {
		int mid = (l + r) >> 1;
		if (Cmp(Adv[o], up, mid)) swap(Adv[o], up);
		if (l == r || Sgn(Adv[o].k - up.k) == 0) return ;

		double x = (Adv[o].b - up.b) / (up.k - Adv[o].k);
		if (x < l || x > r) return ;
		if (x <= mid) Down(lson, up); else Down(rson, up);
	}

	void Insert(int o, int l, int r, int ul, int ur, Line up) {
		if (ul <= l && r <= ur) { Down(o, l, r, up); return ; }
		int mid = (l + r) >> 1;
		if (ul <= mid) Insert(lson, ul, ur, up);
		if (ur > mid) Insert(rson, ul, ur, up);
	}

	Line Query(int o, int l, int r, int qp) {
		if (l == r) return Adv[o];
		int mid = (l + r) >> 1; Line tmp;
		tmp = (qp <= mid ? Query(lson, qp) : Query(rson, qp));
		return Cmp(Adv[o], tmp, qp) ? tmp : Adv[o];
	}
} T;

例题讲解

  1. BZOJ 3165: [HEOI 2013]Segment

    要求在平面直角坐标系下维护两个操作:

    1. 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)
    2. 给定一个数 \(k\) ,询问与直线 \(x = k\) 相交的线段中,交点最靠上的线段的编号。

    这个题就是模板题啦 qwq

    但是网上好多都像我这种 , 把线段基本上视作一条直线 , 从顶至底更新的时候 , 没有判断当前更新的线段 是否覆盖了 \(x=k\) 这个范围 就导致答案失真... (ps : 这个一拍就错啦) 但是官方数据好像很神奇 竟然过啦 qwq

    我太菜啦 , 懒得改啦 , 注意下这个问题就行了 ....

  2. Codeforces Round #463 F. Escape Through Leaf

    又来骗访问啦 qwq 一道好题 2333

posted @ 2018-06-13 21:48  zjp_shadow  阅读(773)  评论(3编辑  收藏  举报