首页 > 其他分享 >李超线段树

李超线段树

时间:2024-08-11 20:04:55浏览次数:12  
标签:结点 标记 int res 线段 tr 李超

P4097 【模板】李超线段树 / [HEOI2013] Segment - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

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

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

考虑线段树。我们对于线段树上的每个结点 \([tl, tr]\),维护标记:

  • 定义域完全包含 \([tl, tr]\) 的线段中,在 \(mid = \lfloor \frac {tl + tr}2 \rfloor\) 处纵坐标最大的一个。

因为我们做标记永久化,所以我们不需要保证这个标记每时每刻都是正确的,只需要保证根到这个结点的路径信息是对的即可。

那么查询时只需从根到叶子,每次和经过的结点维护的线段取答案即可。查询复杂度 \(\mathcal O(\log n)\)。

我们考虑修改,即插入一条线段。我们可以根据这条线段的两个端点求出这条线段所在直线的解析式 \(y = kx + b\)。

首先我们递归到每个被 \([l, r]\) 完全覆盖的极长的线段树中的结点(即普通的线段树修改区间时会访问的结点)。这样的结点有 \(\mathcal O(\log n)\) 个。令这个结点是 \([tl, tr]\)。我们考虑修改这个点(以及其它可能的点)的标记。

如果原来这个结点的标记不存在,或者这个标记完全在新的线段之下,那么直接将这个点的标记修改成新的线段即可。以下讨论的都是原来这个结点的标记存在的情况。

如果原标记完全在新的线段之上,那么没有影响,不需修改。

对于剩下的的情况,原标记一定与新线段交叉。那么我们可以先修改当前点的标记。

此时这个交叉点的位置会影响儿子的标记。具体的,当交叉点横坐标 \(\le mid\) 时,我们应该递归到左儿子继续处理。否则,当交叉点横坐标 \(>mid\) 时,应该递归到右儿子。不难发现这样的修改是 \(\mathcal O(\log n)\) 的。加上最开始的遍历到的线段树的节点数,修改复杂度为 \(\mathcal O(\log^2n)\)。

可读性极差的参考代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int q, cnt;

struct Line {
  long double k, b;
  long double y(int x) {
    return k * x + b;
  }
}a[N];

Line get(int x_0, int y_0, int x_1, int y_1) {    // 通过两个点求直线解析式
  Line res;
  res.k = (long double)(y_0 - y_1) / (x_0 - x_1);
  res.b = y_0 - res.k * x_0;
  return res;
}

int maxx(int x, int y, int k) {    // x,y 这两个函数,在 k 处谁的值更大?
  return a[x].y(k) > a[y].y(k) ? x : y;
}

struct Node {
  int l, r, tag;
}tr[N << 2];

void build(int u, int l, int r) {
  tr[u].l = l, tr[u].r = r;
  if (l != r) {
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  }
}

void update(int u, int k) {    // 尝试通过直线 k 修改 u 的 tag
  cout << tr[u].l << ' ' << tr[u].r << ' ' << k << '\n';
  int mid = tr[u].l + tr[u].r >> 1;
  if (tr[u].l != tr[u].r) {
    if (tr[u].l == 6) cout << a[tr[u].tag].y(tr[u].l) << ' ' << a[k].y(tr[u].l) << '\n';
    if (a[tr[u].tag].y(tr[u].l) >= a[k].y(tr[u].l)) {
      update(u << 1, tr[u].tag);
    }
    if (a[tr[u].tag].y(tr[u].r) >= a[k].y(tr[u].r)) {
      update(u << 1 | 1, tr[u].tag);
    }
  }
  tr[u].tag = maxx(tr[u].tag, k, mid);
}

void modify(int u, int l, int r, int k) {    // 插入了定义域在 [l, r] 内的直线 k
  int mid = tr[u].l + tr[u].r >> 1;
  if (tr[u].l >= l && tr[u].r <= r) {
    int ori_l = a[tr[u].tag].y(tr[u].l);
    int ori_r = a[tr[u].tag].y(tr[u].r);
    int cur_l = a[k].y(tr[u].l);
    int cur_r = a[k].y(tr[u].r);
    
    if (ori_l >= cur_l && ori_r >= cur_r) return;
    if (ori_l < cur_l && ori_r < cur_r) tr[u].tag = k; 
    else {
      if (a[k].y(mid) > a[tr[u].tag].y(mid)) {
        swap(tr[u].tag, k);
        swap(ori_l, cur_l), swap(ori_r, cur_r);
      }
      
      if (cur_l > ori_l) modify(u << 1, l, r, k);
      else modify(u << 1 | 1, l, r, k);
    }
  }
  else {
    if (l <= mid) modify(u << 1, l, r, k);
    if (r > mid) modify(u << 1 | 1, l, r, k);
  }
}

int query(int u, int k) {
  if (tr[u].l == tr[u].r) return tr[u].tag;
  int mid = tr[u].l + tr[u].r >> 1;
  if (k <= mid) return maxx(tr[u].tag, query(u << 1, k), k);
  return maxx(tr[u].tag, query(u << 1 | 1, k), k);
}

int res[N];
int Y[N];

int main() {
  cin >> q;
  
  a[0].k = 0, a[0].b = -114514;
  
  build(1, 1, 40000);
  
  int lst = 0;
  while (q -- ) {
    int op;
    cin >> op;
    if (!op) {
      int k;
      cin >> k;
      k = (k + lst - 1) % 39989 + 1;
      
      int t = query(1, k);
      if (!t || (a[t].y(k) < Y[res[k]] || a[t].y(k) == Y[res[k]] && res[k] < t)) t = res[k];
      cout << (lst = t) << '\n';
    }
    else {
      int x_0, y_0, x_1, y_1;
      cin >> x_0 >> y_0 >> x_1 >> y_1;
      x_0 = (x_0 + lst - 1) % 39989 + 1;
      x_1 = (x_1 + lst - 1) % 39989 + 1;
      y_0 = (y_0 + lst - 1) % 1000000000 + 1;
      y_1 = (y_1 + lst - 1) % 1000000000 + 1;
      
      if (x_0 > x_1) {
        swap(x_0, x_1);
        swap(y_0, y_1);
      }
      a[ ++ cnt] = get(x_0, y_0, x_1, y_1);
      
      if (x_0 != x_1) {
        modify(1, x_0, x_1, cnt);
      }
      else {
        if (y_0 < y_1) swap(y_0, y_1);
        Y[cnt] = y_0;
        if (!res[x_0] || y_0 > Y[res[x_0]]) res[x_0] = cnt;
      }
    }
  }
  
  return 0;
}

标签:结点,标记,int,res,线段,tr,李超
From: https://www.cnblogs.com/2huk/p/18353832

相关文章

  • 线段树:线段树的定义和应用
    引言线段树是一种高级数据结构,用于解决区间查询和更新问题。它在许多应用中都有广泛的使用,例如数组区间的求和、最小值/最大值查询、区间的最小公倍数/最大公约数查询等。本文将详细介绍线段树的定义、构建、应用以及代码实现。目录线段树的定义线段树的应用线段树的构建......
  • P3834 【模板】可持久化线段树 2
    原题链接题解总体上来讲,就是二分\(x\)查询插入\(l-1\)时有多少数小于等于\(x\),查询插入\(r\)时有多少数小于等于\(x\)然后减一减,看看是不是小于等于\(k-1\)我认为目前没有比ai讲的更清楚的了,请点击这里code#include<bits/stdc++.h>usingnamespacestd;/*#def......
  • 线段树优化建图
    今天写了道线段树优化建图的板子题,感觉学算法的还是要记录下来,将来方便复习也算是对竞赛生涯的一种回忆http://codeforces.com/problemset/problem/786/B,洛谷评级还是省选,如果是比赛现场想出来确实厉害,但是现在嘛,已经是时代眼泪了归纳下特点:和区间有关的图论问题直接上代码讲解......
  • 刍议线段树 2 (区间修改,区间查询)
    线段树\(2\)接上一讲https://www.cnblogs.com/yingxilin/p/18350988(没看的同学们可以先看这篇)上一讲里我们已经介绍了单点修改,区间查询的线段树了。在这一讲里,我们开始学习支持区间修改,区间查询的线段树。考虑之前的做法,之前的查询区间会被分为\(O(logn)\),从而求解,但因为......
  • 线段树维护区间方差
    线段树维护区间方差方差区间方差还教室解题思路:利用线段树维护\(a_i\)与\(a_i^2\)\((1\leqi\leqn)\)两个数列,然后使用一个\(lazytag\)来记录是否进行了区间加,最后输出方差的时候使用$$s^2=\sum\limits_{i=1}^n(a_i-\overlinea)^2=(\sum\limits_{i=1}......
  • 洛谷 P3870 开关之线段树板子
    洛谷P3870题解传送锚点摸鱼环节[TJOI2009]开关题目描述现有\(n\)盏灯排成一排,从左到右依次编号为:\(1\),\(2\),……,\(n\)。然后依次执行\(m\)项操作。操作分为两种:指定一个区间\([a,b]\),然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);指定一个区间......
  • 8.9 线段树板子+三分补题+三维的bfs
    nowcoder训练区间线段树板子题,我们只需要把区间每一个点设置成1,然后修改的时候直接改点,然后查区间就行线段树维护最大字段和/01串最大连续1的个数模板题。把白色和黑色看成1/0两个数就行了。#include<bits/stdc++.h>usingnamespacestd;usingi64=longlon......
  • 【线段树合并/树上差分】[P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并
    【线段树合并/树上差分】P4556[Vani有约会]雨天的尾巴/【模板】线段树合并思路对\(x,y,lca(u,v),fa_{lca(u,v)}\)四个点进行树上差分,然后用线段树合并动态权值线段树。#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;template<classNode>str......
  • 线段树合并模板
    template<classNode>structPersidentSegmentTree{#definelc(u)tr[u].l#definerc(u)tr[u].rconstintn;inttot=0;vector<Node>tr;vector<int>root;PersidentSegmentTree():n(0){}PersidentSegmentTree(in......
  • 【题解】Solution Set - NOIP2024集训Day2 线段树
    【题解】SolutionSet-NOIP2024集训Day2线段树https://www.becoder.com.cn/contest/5431「CF1149C」TreeGenerator™结论:对于括号序列的一个子段,删去所有的匹配括号之后,剩下的不匹配的括号,按顺序构成树上的一条路径。Why?从括号序列的构造出发。每次(相当于开始遍历......