「李超线段树」

李超线段树

省选之前的知识了,现在省选苟进队后赶紧补一下

李超线段树是由李超发明的用于求函数定点最值线段树,又名李超树

例题

[HEOI2013]Segment

大意是,在一个二维平面上,依次加入若干条线段,询问对于某个 \(x\) 的最大值,强制在线

李超树像普通线段树一样同样支持两种操作:插入和查询

插入

在李超树上每个节点 \([l,r]\),存了一个优势线段,这条线段在 \(mid\) 的值是最大的

考虑在区间 \([s,t]\) 插入一条线段 \(l_0\),每到一个线段树上的节点,与节点上存的优势线段 \(l_1\) 比较

  • 如果 \(l_0\)\(x=mid\) 上的值比 \(l_1\) 大,显然这个节点的优势线段变成了 \(l_0\)\(swap\;l_0\)\(l_1\)

  • 如果 \(l_0\)\(x=l\) 上的值比 \(l_1\) 大,要么 \(l_0\)\([l,mid]\) 这段区间覆盖了 \(l_1\),或者 \(l_0\)\(l_1\)\([l,mid]\) 中有交点,递归到左区间

  • 如果 \(l_0\)\(x=r\) 上的值比 \(l_1\) 大,要么 \(l_0\)\([mid + 1,r]\) 这段区间覆盖了 \(l_1\),或者 \(l_0\)\(l_1\)\([mid+1,r]\) 中有交点,递归到右区间

\(swap\;l_0\)\(l_1\) 是因为,如果 \(l_0\) 成为了这个区间的优势线段,那么 \(l_1\) 也有可能与 \(l_0\) 从而递归到儿子区间

因为如果 \(l_0\)\(l_1\) 有交,只会有一个交点,所以只会递归进一个儿子,单次插入复杂度是 \(\Theta (\log n)\)

查询

查询对于某个 \(x=k\) 的极值,在李超树上向下递归,用所有包含它的线段树区间的优势线段求出若干个值,一块取个 \(\max\) 就是答案

考虑为什么正确,首先我们插入一条线段时,当且仅当当前的优势线段完全覆盖了这个条线段,才会把这个线段舍去,所以不会影响答案的正确性

然后考虑为什么把路径上的所有值都求一遍,因为这个节点存的线段可能与儿子节点的线段有交点,到底取哪个线段作为最大值并不确定,所以把路径上的都求一遍取个最大值,跟标记永久化差不多

然后这个题就很好解决了,直接套用板子就行了

板子
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
const double EPS = 1e-8;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int T, n, m = 4e4, lastans;
int tree[maxn << 2];

struct Line {
	double k, b;
	Line () {}
	Line (register double x, register double y) { k = x, b = y; }
} a[maxn];

inline double Calc (register Line a, register int x) {
	return a.k * x + a.b;
}

inline void Modify (register int rt, register int l, register int r, register int s, register int t, register int x) { // 插入一条线段
	register int mid = (l + r) >> 1;
	if (s <= l && r <= t) {
		if (Calc (a[x], mid) - Calc (a[tree[rt]], mid) > EPS) swap (tree[rt], x);
		if (Calc (a[x], l) - Calc (a[tree[rt]], l) > EPS) Modify (rt << 1, l, mid, s, t, x);
		if (Calc (a[x], r) - Calc (a[tree[rt]], r) > EPS) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
		return;
	}
	if (s <= mid) Modify (rt << 1, l, mid, s, t, x);
	if (t > mid) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
}

inline int Query (register int rt, register int l, register int r, register int x) { // 单点查询
	if (l == r) return tree[rt];
	register int mid = (l + r) >> 1;
	if (x <= mid) {
		register int tmp = Query (rt << 1, l, mid, x);
		return Calc (a[tmp], x) - Calc (a[tree[rt]], x) > EPS ? tmp : tree[rt];
	} else {
		register int tmp = Query (rt << 1 | 1, mid + 1, r, x);
		return Calc (a[tmp], x) - Calc (a[tree[rt]], x) > EPS ? tmp : tree[rt];
	}
}

int main () {
	T = read();
	while (T --) {
		register int opt = read();
		if (! opt) {
			register int x = (read() + lastans - 1) % 39989 + 1;
			printf ("%d\n", lastans = Query (1, 1, m, x));
		} else {
			register int x0 = (read() + lastans - 1) % 39989 + 1, y0 = (read() + lastans - 1) % 1000000000 + 1;
			register int x1 = (read() + lastans - 1) % 39989 + 1, y1 = (read() + lastans - 1) % 1000000000 + 1;
			if (x0 > x1) swap (x0, x1), swap (y0, y1);
			if (x0 == x1) a[++ n] = Line (0, max (y0, y1));
			else a[++ n] = Line (1.0 * (y1 - y0) / (x1 - x0), y0 - 1.0 * (y1 - y0) / (x1 - x0) * x0);
			Modify (1, 1, m, x0, x1, n);
		}
	}
	return 0;
}

进阶

[SDOI2016]游戏

首先看到 \(a\times dis+b\),是一个一次函数的形式,不妨考虑先树剖一下,然后用李超树维护最大值

对于每次插入操作,先求出 \(s\)\(t\)\(lca\),预处理出每个点到根节点的距离 \(dis[u]\),然后化简一下式子

对于 \(s\)\(lca\) 的那条路径上的点 \(u\),增加的贡献是

\[a\times (dis[s]-dis[u])+b \]

\[=-a\times dis[u] + a\times dis[s] +b \]

对于 \(t\)\(lca\) 的那条路径上的点,增加的贡献是

\[a\times (dis[s]+dis[u]-2\times dis[lca])+b \]

\[=a\times dis[u] + a\times (dis[s]-2\times dis[lca]) +b \]

然后就可以直接插入了

现在问题就剩如果维护区间最小值,容易发现对于一个区间内的线段,最小值无非就是从两个端点的地方取到,所以线段树上再维护一个区间最小值,每次 \(Pushup\),最后区间查询的时候,经过的区间上存的优势线段也能为答案作出贡献,算上就好了

代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

typedef long long ll;

using namespace std;

const int maxn = 2e5 + 50;
const ll INF = 123456789123456789;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m;

struct Line {
	int k;
	ll b;
	Line () {}
	Line (register int x, register ll y) { k = x, b = y; }
};

struct Tree {
	Line seg;
	ll val;
	Tree () { seg.k = 0, seg.b = val = INF; }
} tree[maxn << 2];

inline ll Calc (register Line a, register ll x) { return a.k * x + a.b; }

struct Edge {
	int to, next, w;
} e[maxn << 1];

int tot, head[maxn];

inline void Add (register int u, register int v, register int w) {
	e[++ tot].to = v;
	e[tot].w = w;
	e[tot].next = head[u];
	head[u] = tot;
}

int deep[maxn], size[maxn], son[maxn], f[maxn];
int tic, top[maxn], dfn[maxn], rk[maxn];
ll dis[maxn];

inline void DFS0 (register int u, register int fa) {
	deep[u] = deep[fa] + 1, size[u] = 1;
	for (register int i = head[u]; i; i = e[i].next) {
		register int v = e[i].to;
		if (v == fa) continue;
		dis[v] = dis[u] + e[i].w, f[v] = u, DFS0 (v, u), size[u] += size[v];
		if (size[son[u]] < size[v]) son[u] = v;
	}
}

inline void DFS1 (register int u, register int t) {
	top[u] = t, dfn[u] = ++ tic, rk[tic] = u;
	if (son[u]) DFS1 (son[u], t);
	for (register int i = head[u]; i; i = e[i].next) {
		register int v = e[i].to;
		if (v == f[u] || v == son[u]) continue;
		DFS1 (v, v);
	}
}

inline int LCA (register int u, register int v) {
	while (top[u] != top[v]) {
		if (deep[top[u]] < deep[top[v]]) swap (u, v);
		u = f[top[u]];
	}
	return deep[u] < deep[v] ? u : v;
}

inline void Pushup (register int rt, register int l, register int r) {
	tree[rt].val = min (tree[rt].val, min (Calc (tree[rt].seg, dis[rk[l]]), Calc (tree[rt].seg, dis[rk[r]])));
	tree[rt].val = min (tree[rt].val, min (tree[rt << 1].val, tree[rt << 1 | 1].val));
}

inline void Modify (register int rt, register int l, register int r, register int s, register int t, register Line x) {
	register int mid = (l + r) >> 1;
	if (s <= l && r <= t) {
		if (Calc (x, dis[rk[mid]]) < Calc (tree[rt].seg, dis[rk[mid]])) swap (tree[rt].seg, x);
		if (Calc (x, dis[rk[l]]) < Calc (tree[rt].seg, dis[rk[l]])) Modify (rt << 1, l, mid, s, t, x);
		if (Calc (x, dis[rk[r]]) < Calc (tree[rt].seg, dis[rk[r]])) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
		return Pushup (rt, l, r), void ();
	}
	if (s <= mid) Modify (rt << 1, l, mid, s, t, x);
	if (t > mid) Modify (rt << 1 | 1, mid + 1, r, s, t, x);
	Pushup (rt, l, r);
}

inline ll Query (register int rt, register int l, register int r, register int s, register int t) {
	if (s <= l && r <= t) return tree[rt].val;
	register int mid = (l + r) >> 1;
	register ll ans = min (Calc (tree[rt].seg, dis[rk[max (l, s)]]), Calc (tree[rt].seg, dis[rk[min (r, t)]]));
	if (s <= mid) ans = min (ans, Query (rt << 1, l, mid, s, t));
	if (t > mid) ans = min (ans, Query (rt << 1 | 1, mid + 1, r, s, t));
	return ans;
}

inline void TreeModify (register int u, register int v, register Line x) {
	while (top[u] != top[v]) {
		if (deep[top[u]] < deep[top[v]]) swap (u, v);
		Modify (1, 1, n, dfn[top[u]], dfn[u], x), u = f[top[u]];
	}
	if (dfn[u] > dfn[v]) swap (u, v);
	Modify (1, 1, n, dfn[u], dfn[v], x);
}

inline ll TreeQuery (register int u, register int v, register ll ans = INF) {
	while (top[u] != top[v]) {
		if (deep[top[u]] < deep[top[v]]) swap (u, v);
		ans = min (ans, Query (1, 1, n, dfn[top[u]], dfn[u])), u = f[top[u]];
	}
	if (dfn[u] > dfn[v]) swap (u, v);
	return min (ans, Query (1, 1, n, dfn[u], dfn[v]));
}

int main () {
	n = read(), m = read();
	for (register int i = 1, u, v, w; i <= n - 1; i ++) 
		u = read(), v = read(), w = read(), Add (u, v, w), Add (v, u, w);
	DFS0 (1, 0), DFS1 (1, 1);
	while (m --) {
		register int opt = read(), u = read(), v = read();
		if (opt == 1) {
			register int a = read(), b = read(), lca = LCA (u, v);
			TreeModify (u, lca, Line (- a, a * dis[u] + b));
			TreeModify (lca, v, Line (a, a * dis[u] - 2 * a * dis[lca] + b));
		} else printf ("%lld\n", TreeQuery (u, v));
	}
	return 0;
}
posted @ 2021-04-23 13:20  Rubyonlу  阅读(221)  评论(2编辑  收藏  举报