常用算法题锦——线段树

线段树

最好的宝石

题目:

单值修改,区间查询,维护信息:最大值和最大值的个数

题目描述 
牛牛有n个宝石,第i个宝石的价值是w[i].
有m个操作,操作分为两种类型
− Change   x  y   把第x个宝石的价值改成 y 
− Ask          l   r   询问区间[l,r]内宝石的最大价值,和最大价值的宝石有多少个。
输入描述:
第一行两个整数 n , m (1 ≤ n,m ≤ 2e5)
第二行有n个整数 w[i]  (0 ≤ w[i] ≤ 1e9)
接下来m行,每行代表一个操作。具体见题目描述。
输出描述:
每次询问输出一行,每行两个整数 val  cnt,val代表所有宝石中的最大价值,cnt代表价值最大的宝石有多少个。
示例1
输入
复制
5 3
2 4 3 6 8
Ask 1 5
Change 2 10
Ask 1 3
输出
复制
8 1
10 1  

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m;
int w[N];
struct Node{
  int l, r;
  int mx, cnt;
}tr[N * 4];
struct Seg_Tree{
  void pushup(Node &c, Node &a, Node &b)
  {
    if (a.mx == b.mx) c.cnt = a.cnt + b.cnt;
    else if (a.mx > b.mx) c.cnt = a.cnt;
    else c.cnt = b.cnt;
    c.mx = max(a.mx, b.mx);
  }
  void pushup(int u)
  {
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
  }
  void build(int u, int l, int r)
  {
    if (l == r)
    {
      tr[u] = {l, r, w[l], 1};
      return;
    }
    tr[u] = {l, r};
    int mid = l + r >> 1;
    build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
    pushup(u);
  }
  void change(int u, int x, int c)
  {
    if  (tr[u].l == x && tr[u].r == x)
    {
      tr[u] = {x, x, c, 1};
      return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid) change(u << 1, x, c);
    if (x > mid) change(u << 1 | 1, x, c);
    pushup(u);
  }
  Node query(int u, int l, int r)
  {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid) return query(u << 1, l, r);
    else if (l > mid) return query(u << 1 | 1, l, r);
    else
    {
      Node left = query(u << 1, l, r);
      Node right = query(u << 1 | 1, l, r);
      Node ans;
      pushup(ans, left, right);
      return ans;
    }
  }
}t;
int main(void)
{
  cin >> n >> m;
  for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
  t.build(1, 1, n);
  char op[10];
  int l, r;
  while (m -- )
  {
    scanf("%s", op);
    scanf("%d%d", &l, &r);
    if (*op == 'C')
    {
      t.change(1, l, r);
    }
    else
    {
      Node x = t.query(1, l, r);
      printf("%d %d
", x.mx, x.cnt);
    }
  }
  return 0;
}

  

 扫描线:

【模板】扫描线

题目:

题目描述

求 n 个矩形的面积并。

输入格式

第一行一个正整数 n。

接下来 n 行每行四个非负整数x1,y1,x2,y2,表示一个矩形的左下角坐标为 (x1,y1),右上角坐标为 (x2,y2)。

输出格式

一行一个正整数,表示 n 个矩形的并集覆盖的总面积。

输入输出样例

输入 #1
2
100 100 200 200
150 150 250 255
输出 #1
18000

说明/提示

对于 20% 的数据,1n1000。
对于 100% 的数据,11n105,1<x2109,0y1<y2109。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n;
struct Segment{
  int x, y1, y2, mark;
  bool operator< (const Segment &W) const{
    return x < W.x;
  }
}seg[N << 1];
struct Node{
  int l, r;
  int tag, len;
}tr[N << 3];
vector<int> ys;
void build(int u, int l, int r)
{
  tr[u] = {l, r, 0, 0};
  if (l == r) return;
  int mid = l + r >> 1;
  build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void pushup(int u)
{
  if (tr[u].tag)
  {
    tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
  }
  else
  {
    if (tr[u].l == tr[u].r) tr[u].len = 0;
    else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
  }
}
void update(int u, int l, int r, int c)
{
  if (tr[u].l >= l && tr[u].r <= r)
  {
    tr[u].tag += c;
    pushup(u);
    return;
  }
  int mid = tr[u].l + tr[u].r >> 1;
  if (l <= mid) update(u << 1, l, r, c);
  if (r > mid) update(u << 1 | 1, l, r, c);
  pushup(u);
}
int find(int y)
{
  return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}
int main()
{
  cin >> n;
  for (int i = 0, j = 0; i < n; i ++ )
  {
    int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    seg[j ++ ] = {x1, y1, y2, 1}, seg[j ++ ] = {x2, y1, y2, -1};
    ys.push_back(y1), ys.push_back(y2);
  }
  sort(ys.begin(), ys.end());
  ys.erase(unique(ys.begin(), ys.end()), ys.end());
  build(1, 0, ys.size() - 2);
  sort(seg, seg + 2 * n);
  long long ans = 0;
  for (int i = 0; i < 2 * n; i ++ )
  {
    if (i) ans += 1ll * tr[1].len * (seg[i].x - seg[i - 1].x);
    update(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].mark);
  }
  printf("%lld
", ans);
  return 0;
}

  

 Hopping Rabbit

题目:

平面上有n个矩形,给定d,需要找到一个位置(x,y)使得所有的(x + kd, y + kd)均不落在矩形中

分析:

扫描线

我们在使用扫描线的时候,以线段树维护y轴为标准,x是使用点坐标来计算宽的,而y是使用线段也就是区间来计算的。

所以,我们这里对x2-1,y2-1,但是在建立segment的时候,x2还要再加回来,但是,y2 不用加回来,y2-1正好就是线段的长度

而我们的整个正方形区域,按坐标来看,是从[0, 0]到[d - 1,d - 1]的,但是,我们的横坐标,是0~d - 1,但是纵坐标是用线段树维护的,长度是0到d-2

对于x2也要和y2一样都要先-1的另一个原因是,将x2从1~n映射到0~n-1,可以在取模的时候更精准。比如,x1 = 1, x2 = d, 在这种情况下,取模,就得到了x1 = 1, x2 = 0,很显然是不符合我们需要的,这种情况,我们要保证x1小于x2 的,所以,我们将值往前移动一格就完美的解决了这个问题了。而这个技巧也是很常见的。

比如将1~n的数字,映射到二维表格中,我们将原本的1~n先映射为0~n-1,再对每个数取模和下取整的时候,就是很符合规范的映射了,这里就不在赘述了。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, d, ql, qr, mark;
struct Node{
  int tag, len;
}tr[N << 2];
struct Segment{
  int y1, y2, mark;
};
vector<Segment> vt[N];
void pushup(int l, int r, int rt)
{
  if (tr[rt].tag) tr[rt].len = r - l + 1;
  else if (l == r) tr[rt].len = 0;
  else tr[rt].len = tr[rt << 1].len + tr[rt << 1 | 1].len;
}
void add(int x1, int x2, int y1, int y2)
{
  vt[x1].push_back((Segment){y1, y2, 1});
  vt[x2 + 1].push_back((Segment){y1, y2, -1});
}
void update(int l, int r, int rt)
{
  if (ql <= l && r <= qr)
  {
    tr[rt].tag += mark;
    pushup(l, r, rt);
    return;
  }
  int mid = l + r >> 1;
  if (ql <= mid) update(l, mid, 2 * rt);
  if (qr > mid) update(mid + 1, r, 2 * rt + 1);
  pushup(l, r, rt);
}
void query(int l, int r, int rt)
{
  if(tr[rt].len == 0)
  {
    printf("%d
", l);
    return;
  }
  int mid = l + r >> 1;
  if (tr[2 * rt].len < mid - l + 1) query(l, mid, 2 * rt);
  else query(mid + 1, r, 2 * rt + 1);
}
int md(int x)
{
  return (x % d + d) % d;
}
int main()
{
  cin >> n >> d;
  for (int i = 1; i <= n; i ++ )
  {
    int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    x2 --, y2 --;
    if (x2 - x1 + 1 >= d) x1 = 0, x2 = d - 1;
    if (y2 - y1 + 1 >= d) y1 = 0, y2 = d - 1;
    x1 = md(x1);
    y1 = md(y1);
    x2 = md(x2);
    y2 = md(y2);
    if (x1 <= x2)
    {
      if (y1 <= y2) add(x1, x2, y1, y2);
      else add(x1, x2, 0, y2), add(x1, x2, y1, d - 1);
    }
    else
    {
      if (y1 <= y2) add(0, x2, y1, y2), add(x1, d - 1, y1, y2);
      else add(0, x2, 0, y2), add(0, x2, y1, d - 1), add(x1, d - 1, 0, y2), add(x1, d - 1, y1, d- 1);
    }
  }
  for (int i = 0; i < d; i ++ )
  {
    for (int j = 0; j < vt[i].size(); j ++ )
    {
      ql = vt[i][j].y1;
      qr = vt[i][j].y2;
      mark = vt[i][j].mark;
      update(0, d - 1, 1);
    }
    if (tr[1].len < d)
    {
      puts("YES");
      printf("%d ", i);
      query(0, d - 1, 1);
      return 0;
    }
  }
  puts("NO");
  return 0;
}

 

下面是逆十字的做法,我在懒标记那里还是有些不懂为什么要这么做的

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 100005;
int n, d;
vector<PII> op0[N], op1[N];
int md(int x)
{
  return (x % d + d) % d;
}
//tg为懒标记,
int t[N * 4], tg[N * 4];//t表示当前区间是否有空白,由子节点更新而来,tg表示以u为根节点所再区间是不是全1
//u根节点,l和r表示节点u的左右端点,x和y表示修改区间,c表示求改为c
void update(int u, int l, int r, int x, int y, int c)
{
  //当前以u为跟节点的整个区间,都是有c的
  if (l >= x && r <= y)
  {
    t[u] += c, tg[u] += c;
    return;
  }
  int mid = l + r >> 1;
  if (x <= mid) update(u << 1, l, mid, x, y, c);
  if (y > mid) update(u << 1 | 1, mid + 1, r, x, y, c);
  t[u] = min(t[u << 1], t[u << 1 | 1]) + tg[u];
  //t[u] == 0表示左子树或者有子树有空白部分,而且u所在区间没有被标记
}
//找到空白部分
int query(int u, int l, int r)
{
  if (l == r) return l;
  int mid = l + r >> 1;
  if (tg[u] + t[u << 1] == t[u]) return query(u << 1, l, mid);
  return query(u << 1 | 1, mid + 1, r);
}
int main()
{
  scanf("%d%d", &n, &d);
  for (int i = 1; i <= n; i ++ )
  {
    int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    vector<PII> range1, range2;
    x2 --, y2 --;
    if (x2 - x1 + 1 >= d) x1 = 0, x2 = d - 1;
    if (y2 - y1 + 1 >= d) y1 = 0, y2 = d - 1;
    x1 = md(x1), y1 = md(y1), x2 = md(x2), y2 = md(y2);
    if (x2 - x1 + 1 >= d) range1.push_back({0, d - 1});
    else if (x2 >= x1) range1.push_back({x1, x2});
    else {
      range1.push_back({x1, d - 1});
      range1.push_back({0, x2});
    }
    if (y2 - y1 + 1 >= d) range2.push_back({0, d - 1});
    else if (y2 >= y1) range2.push_back({y1, y2});
    else {
      range2.push_back({y1, d - 1});
      range2.push_back({0, y2});
    }
    for (auto j : range1)
      for (auto k : range2)
      {
        op0[j.first].push_back(k);
        op1[j.second + 1].push_back(k);
      }
  }
  for (int i = 0; i < d; i ++ )
  {
    //将x=i这一列的所有信息都更新,然后判断这一行是否有空白
    for (auto j : op0[i])
      update(1, 0, d - 1, j.first, j.second, 1);
    for (auto j : op1[i])
      update(1, 0, d - 1, j.first, j.second, -1);
    if (t[1] == 0)
    {
      puts("YES");
      printf("%d %d
", i, query(1, 0, d - 1));
      return 0;
    }
  }
  puts("NO");
  return 0;
}

  

原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14998007.html