李超线段树

OI-wiki Link

直线型

先看一种题型:

  • 给出 \(k, b\),加入一条 \(y = k \cdot x + b\) 的直线。
  • 给出 \(a\), 查询与 \(x = a\) 相交的直线中纵坐标最大/最小值。

似乎是一种区间修改,单点查询?考虑使用线段树维护。

但对于不同的 \(x\),不同的 \(k, b\) 造成的效果不一定是呈现出一种“压制”的态势,可以是类似于对于 \(x < mid\)\(k_1 \cdot x + b_1 > k_2 \cdot x + b_2\),反之 \(k_1 \cdot x + b_1 < k_2 \cdot x + b_2\)

这种情况下就需要使用另外一种线段树——李超线段树。

基本构成

在每个节点存储一组 \(k,b\),表示在 \(x=mid\) 的情况下,\(k\cdot x + b\) 可以取到最值。

但注意我这里的 \(k,b\) 不作为懒标记下传,采用的是永久化标记的写法。

可以使用动态开点来优化。

操作二

和传统线段树差别不大。由于是永久化标记的写法,只需要把从根节点到 \([x,x+1)\) 的所有节点上的 \(k,b\) 都去更新一下答案即可。

为了避免查询无效的节点,如果当前节点没法继续走下去(即对应节点的 \(b\) 取到了极值),则直接退出即可。

操作一

假设当前节点为 \(id\),区间对应 \([l,r)\)注意是左闭右开。

  • 如果当前节点没有存储任何的 \(k,b\) (可以采取给 \(b\) 初始化为极大/极小值来解决),则直接赋值即可。
  • 否则,看对于 \(x=mid\) 时,当前节点和修改的 \(k,b\) 那种情况更优,将更优的存储在节点 \(id\) 上,更劣的作为修改的 \(k,b\),注意不是直接舍弃。这时有三种情况。
    • \(k=tr_{id}.k\),那么无论如何,也不可能用手上的 \(k,b\) 来更新答案,直接退出即可。
    • \(k>tr_{id}.k\),如果是求最大值,则去试图更新 \(id \times 2 + 1,[mid, r)\),否则去试图更新\(id \times 2,[l,mid)\)
    • \(k<tr_{id}.k\),如果是求最小值,则去试图更新 \(id \times 2 + 1,[mid, r)\),否则去试图更新\(id \times 2,[l,mid)\)

复杂度

加上上面所说的所有优化后,可以发现每条线段最多影响 \(1\) 个节点,所以空间复杂度是 \(O(n)\)

但查询仍有可能跑满 \(\log V\) 层,所以时间复杂度是 \(O(n \log V)\) 的。

线段型

如果加入的不是直线而是一条在 \([l,r)\) 的线段呢?

其实和上面差不多,但你需要找到那条线段对应每个的区间,再在这个区间内做一下直线型修改即可。

时间负杂度和空间复杂度都比上面多乘一个 \(\log V\)

例题

contest

点击查看代码
// https://vjudge.d0j1a1701.cc/problem/Yosupo-segment_add_get_min
#include <bits/stdc++.h>
//#define _1 (__int128)1
//#define int long long

using namespace std;
using ll = long long;

void FileIO (const string s) {
  freopen(string(s + ".in").c_str(), "r", stdin);
  freopen(string(s + ".out").c_str(), "w", stdout);
}

const int P = 1e9;
const ll INF = 4e18;

struct SegTree {
  int ls, rs, k;
  ll b = INF;
} ;

int n, q, ndcnt = 1, op, x, l, r;
ll ans, y, b;
vector<SegTree> tr;

int ls (int id) {
  if (!tr[id].ls) tr.push_back({}), tr[id].ls = ++ndcnt;
  return tr[id].ls;
}

int rs (int id) {
  if (!tr[id].rs) tr.push_back({}), tr[id].rs = ++ndcnt;
  return tr[id].rs;
}

void modify (int id, int l, int r, int k, ll b, int x, int y) {
  if (l > y || r <= x || b == INF) return ;
  if (l >= x && r <= y + 1 && tr[id].b == INF) {
    tr[id].k = k, tr[id].b = b;
    return ;
  }
  int mid = l + (r - l) / 2;
  if (l >= x && r <= y + 1 && 1ll * tr[id].k * mid + tr[id].b > 1ll * k * mid + b)
    swap(tr[id].k, k), swap(tr[id].b, b);
  if ((tr[id].k == k && tr[id].b <= b) || l + 1 == r)
    return ;
  if (!(l >= x && r <= y + 1) || tr[id].k < k) modify(ls(id), l, mid, k, b, x, y);
  if (!(l >= x && r <= y + 1) || tr[id].k > k) modify(rs(id), mid, r, k, b, x, y);
}

void Query (int x) {
  int id = 1, l = -P, r = P + 1;
  while (1) {
    ans = min(ans, 1ll * tr[id].k * x + tr[id].b);
    int mid = l + (r - l) / 2;
    if (mid > x && tr[id].ls) id = ls(id), r = mid;
    else if (mid <= x && tr[id].rs) id = rs(id), l = mid;
    else break;
  }
}

signed main () {
  ios::sync_with_stdio(0), cin.tie(0);
  // FileIO("");
  cin >> n >> q, tr.push_back({}), tr.push_back({});
  for (int i = 1, k, x, y; i <= n; i++) {
    cin >> x >> y >> k >> b;
    modify(1, -P, P + 1, k, b, x, y - 1);
  }
  while (q--) {
    cin >> op >> l;
    if (op) {
      ans = INF;
      Query(l);
      if (ans == INF) {
        cout << "INFINITY\n";
        continue;
      }
      cout << ans << '\n';
    } else {
      cin >> r >> x >> y;
      modify(1, -P, P + 1, x, y, l, r - 1);
    }
  }
  return 0;
}
posted @ 2024-09-08 11:39  wnsyou  阅读(11)  评论(0编辑  收藏  举报