线段树

丧失修改:ST 表

丧失差分:树状数组

丧失 \(k\) 大:堆

所以,我们需要线段树!

让我们分析一下:区间修改不能影响太多区间,区间查询也是一样。

所以线段树,就是通过只保留重要的区间来快速查询的一个算法。

如下是一个 \(N = 8\) 的线段树:

线段树结构.bmp

(好吧,画的有点不行,但这是值得的(我有了无限草稿纸))

线段树的区间查询,就是 DFS,DFS 到一个子区间直接返回并合并答案,DFS 到一个不交的区间直接返回(不合并)。

但是,没有修改的数据结构是没有灵魂的——hhc,线段树最实用的地方还是它加上区间修改后仍然能保证 \(O(\log N)\)(而不是暴力的 \(O(N \log N)\))。

区间修改

我们引入一个东西,叫做懒标记(可以扩展!)。对于某个区间的修改,我们一样拆成一些子区间然后对这些子区间(及其上面)处理修改。而子区间下面的信息则由懒标记来维护。

不管怎样,原则要加上一条:如果我们访问到的节点懒标记,并且这个节点还要往下递归,那么下传懒标记。

我打个比方吧,懒标记就像是上司欠员工的钱。

领导下来巡查了,上级瑟瑟发抖,于是还清了自己的债务(这就是懒标记下传)。

常见错误

  1. 懒标记下传后未清空

  2. 没判断两个线段不交

LG 3373 【模板】线段树 2 link:https://www.luogu.com.cn/problem/P3373
#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, q, m, arr[100010], d[500050], op, x, y, k;

struct tag {
  int mul, add;
}t[500050];

struct mergifyd {
  int operator()(int a, int b) {
    return (a + b) % m;
  }
}mrgd;

struct mergifyt {
  tag operator()(tag a, tag b) {
    return {a.mul * b.mul % m, (a.add * b.mul + b.add) % m};
  }
}mrgt;

void pushdn(int id, int l, int r) {
  t[id * 2] = mrgt(t[id * 2], t[id]);
  t[id * 2 + 1] = mrgt(t[id * 2 + 1], t[id]);
  int mid = (l + r) >> 1;
  d[id * 2] = (d[id * 2] * t[id].mul + t[id].add * (mid - l + 1)) % m;
  d[id * 2 + 1] = (d[id * 2 + 1] * t[id].mul + t[id].add * (r - mid)) % m;
  t[id] = {1, 0};
}

void build(int id, int l, int r) {
  t[id] = {1, 0};
  if(l == r) {
    d[id] = arr[l];
    return ;
  }
  int mid = (l + r) >> 1;
  build(id * 2, l, mid);
  build(id * 2 + 1, mid + 1, r);
  d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
  //cout << id << ' ' << d[id] << '\n';
}

void mul(int id, int l, int r, int ql, int qr, int x) {
  if(r < ql || qr < l) {
    return ;
  }
  if(ql <= l && r <= qr) {
    t[id] = mrgt(t[id], {x, 0});
    d[id] = (d[id] * x) % m;
    return ;
  }
  pushdn(id, l, r);
  int mid = (l + r) >> 1;
  mul(id * 2, l, mid, ql, qr, x);
  mul(id * 2 + 1, mid + 1, r, ql, qr, x);
  d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
}

void add(int id, int l, int r, int ql, int qr, int x) {
  //cout << id << ' ' << l << ' ' << r << ' ' << ql << ' ' << qr << ' ' << x << '\n';
  if(r < ql || qr < l) {
    //cout << "out" << '\n';
    return ;
  }
  if(ql <= l && r <= qr) {
    //cout << "in" << '\n';
    t[id] = mrgt(t[id], {1, x});
    d[id] = (d[id] + (r - l + 1) * x) % m;
    return ;
  }
  pushdn(id, l, r);
  int mid = (l + r) >> 1;
  add(id * 2, l, mid, ql, qr, x);
  add(id * 2 + 1, mid + 1, r, ql, qr, x);
  d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
}

int query(int id, int l, int r, int ql, int qr) {
  if(r < ql || qr < l) {
    return 0;
  }
  if(ql <= l && r <= qr) {
    return d[id];
  }
  pushdn(id, l, r);
  int mid = (l + r) >> 1;
  return mrgd(query(id * 2, l, mid, ql, qr), query(id * 2 + 1, mid + 1, r, ql, qr));
}

signed main() {
  cin >> n >> q >> m;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
  }
  build(1, 1, n);
  //for(int i = 1; i <= 9; i++) {
    //cout << d[i] << ',' << t[i].mul << ',' << t[i].add << ' ';
  //}
  //cout << '\n';
  for(; q--; ) {
    cin >> op >> x >> y;
    if(op == 3) {
      cout << query(1, 1, n, x, y) << '\n';
    }else {
      cin >> k;
      if(op == 1) {
        mul(1, 1, n, x, y, k);
      }else {
        add(1, 1, n, x, y, k);
      }
    }
    //for(int i = 1; i <= 9; i++) {
      //cout << d[i] << ',' << t[i].mul << ',' << t[i].add << ' ';
    //}
    //cout << '\n';
  }
  return 0;
}
posted @ 2024-04-02 22:25  hhc0001  阅读(1)  评论(0编辑  收藏  举报