• 「李超线段树」


    李超线段树

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

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

    例题

    [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
    ", 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 imes dis+b),是一个一次函数的形式,不妨考虑先树剖一下,然后用李超树维护最大值

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

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

    [a imes (dis[s]-dis[u])+b ]

    [=-a imes dis[u] + a imes dis[s] +b ]

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

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

    [=a imes dis[u] + a imes (dis[s]-2 imes 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
    ", TreeQuery (u, v));
    	}
    	return 0;
    }
    
  • 相关阅读:
    jquery 获取当前元素的索引值
    JQuery中根据属性或属性值获得元素(6种情况获取方法)
    jquery如何获取某一个兄弟节点
    JAVA学习<六>
    JAVA学习<四>
    JAVA学习<三>
    iOS定位到崩溃代码行数
    Swift3.0基础语法学习<五>
    Swift3.0基础语法学习<四>
    Swift3.0基础语法学习<三>
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/14693361.html
Copyright © 2020-2023  润新知