• 常用算法题锦——线段树


    线段树

    最好的宝石

    题目:

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

    题目描述 
    牛牛有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;
    }
    

      

  • 相关阅读:
    Go
    go-反射
    go-map
    go中数组与切片
    goroutine 和 channel
    Go中的闭包
    新版 C# 高效率编程指南
    gitignore 规则和不起作用的解决方案
    Linux系统部署.Net Core3.1项目
    List<对象> 根据某个字段提出一个 List<String>,并且去重
  • 原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14998007.html
Copyright © 2020-2023  润新知