单点修改+区间查询线段树

这个没有标记,还是比较好写的。

  1. //P3374 【模板】树状数组 1
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. #define LL long long
  5. const int N = 1000010;
  6. struct Node {
  7. int l, r;
  8. LL sum;
  9. } a[N << 2];
  10. #define ls(x) (x << 1)
  11. #define rs(x) (x << 1 | 1)
  12. inline void pushup(int x) {
  13. a[x].sum = a[ls(x)].sum + a[rs(x)].sum;
  14. }
  15. void build(int x, int l, int r)
  16. {
  17. a[x].l = l, a[x].r = r;
  18. if (l == r) {
  19. scanf("%lld", &a[x].sum);
  20. return;
  21. }
  22. int mid = (l + r) >> 1;
  23. build(ls(x), l, mid);
  24. build(rs(x), mid + 1, r);
  25. pushup(x);
  26. }
  27. LL query(int x, int l, int r) {
  28. if (l <= a[x].l && a[x].r <= r)
  29. return a[x].sum;
  30. int mid = (a[x].l + a[x].r) >> 1;
  31. LL res = 0;
  32. if (l <= mid) res += query(ls(x), l, r);
  33. if (r > mid) res += query(rs(x), l, r);
  34. return res;
  35. }
  36. void change(int x, int p, LL val) {
  37. if (a[x].l == a[x].r) {
  38. a[x].sum += val;
  39. return;
  40. }
  41. int mid = (a[x].l + a[x].r) >> 1;
  42. if (p <= mid) change(ls(x), p, val);
  43. if (p > mid) change(rs(x), p, val);
  44. pushup(x);
  45. }

对于单点修改,除了从根节点向下找,我们还可以从叶子节点向上修改,如下:

int mp[N];
//mp记录的是某叶子节点在线段树对应的节点的序号,可以在build的时候一并算出
//在l==r的时候记mp[l] = x 或者 mp[r] = x 即可
//这种写法虽然要多开一个mp数组,但是写起来快,而且跑的速度常数也要小一些
void change(int p, LL val) {
    int x = mp[p];
    a[x].sum += val;
    while (x >>= 1) pushup(x);
}

区间修改+区间查询线段树

//P3372
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 100010;
int n, m;
struct node {
    int l, r, len;
    LL val, tag;
} a[N << 2];
inline int ls(int x) { return x << 1; };
inline int rs(int x) { return x << 1 | 1; };
inline void pushup(int x) {
    a[x].val = a[ls(x)].val + a[rs(x)].val;
}
void build(int x = 1, int l = 1, int r = n) {
    a[x].l = l, a[x].r = r, a[x].len = r - l + 1;
    if (l == r) {
        scanf("%lld", &a[x].val);
        return;
    }
    int mid = (l + r) >> 1;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    pushup(x);
}
inline void f(int x, int k) {
    a[x].tag += k;
    a[x].val += k * a[x].len;
}
inline void pushdown(int x) {
    f(ls(x), a[x].tag);
    f(rs(x), a[x].tag);
    a[x].tag = 0;
}
void update(int l, int r, LL val, int x);
LL query(int l, int r, int x);
int main()
{
    scanf("%d%d", &n, &m);
    build();

    while (m--) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if (opt == 1) {
            LL val;
            scanf("%lld", &val);
            update(l, r, val, 1);
        }
        else printf("%lld\n", query(l, r, 1));
    }
    return 0;
}
void update(int l, int r, LL val, int x)
{
    if (l <= a[x].l && a[x].r <= r) {
        a[x].val += a[x].len * val;
        a[x].tag += val;
        return;
    }
    pushdown(x);
    int mid = (a[x].l + a[x].r) >> 1;
    if (l <= mid) update(l, r, val, ls(x));
    if (r >  mid) update(l, r, val, rs(x));
    pushup(x);
    return;
}
LL query(int l, int r, int x)
{
    if (l <= a[x].l && a[x].r <= r)
        return a[x].val;
    pushdown(x);
    int mid = (a[x].l + a[x].r) >> 1;
    LL ans = 0;
    if (l <= mid) ans += query(l, r, ls(x));
    if (r >  mid) ans += query(l, r, rs(x));
    return ans;
}

多标记线段树

下面给出一个同时处理区间加,区间乘,和查询区间和的线段树模板。
(本质上就是多存几个tag,维护好他们的先后关系,查询或者修改的时候记得下传即可)

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
#define LL long long
//题目中给的p,作为模数的,从数据中读入
int p;
LL a[N];
//线段树结构体,v表示此时的答案,mul表示乘法意义上的lazytag,add是加法意义上的
struct Node {
    int l, r, len;
    LL v, mul, add;
} st[N << 2];
inline int ls(int x) { return x << 1; }
inline int rs(int x) { return x << 1 | 1; }
//buildtree
inline void pushup(int x) {
    st[x].v = (st[ls(x)].v + st[rs(x)].v) % p;
}
void build (int x, int l, int r) {
    Node &node = st[x];
    node.mul = 1, node.add = 0;
    node.l = l, node.r = r, node.len = r - l + 1;
    if (l == r) {
        node.v = a[l];
        return;
    }
    int mid = (l + r) / 2;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    pushup(x);
}
//核心代码,维护lazytag
void solve(int x, LL k1, LL k2) {
    Node &node = st[x];
    node.v = (node.v * k1 + k2 * node.len) % p;
    node.mul = (node.mul * k1) % p;
    node.add = (node.add * k1 + k2) % p;
}
void pushdown(int x) {
    solve(ls(x), st[x].mul, st[x].add);
    solve(rs(x), st[x].mul, st[x].add);
    st[x].mul = 1, st[x].add = 0;
}
//update1:乘法
void update1(int x, int l, int r, LL k) {
    if (l <= st[x].l && st[x].r <= r) {
        solve(x, k, 0);
        return;
    }
    pushdown(x);
    int mid = (st[x].l + st[x].r) / 2;
    if (l <= mid) update1(ls(x), l, r, k);
    if (r >  mid) update1(rs(x), l, r, k);
    pushup(x);
}
//update2,加法,和乘法同理
void update2(int x, int l, int r, LL k) {
    if (l <= st[x].l && st[x].r <= r) {
        solve(x, 1, k);
        return;
    }
    pushdown(x);
    int mid = (st[x].l + st[x].r) / 2;
    if (l <= mid) update2(ls(x), l, r, k);
    if (r >  mid) update2(rs(x), l, r, k);
    pushup(x);
}
//query:查询
long long query(int x, int l, int r)
{
    if (l <= st[x].l && st[x].r <= r) return st[x].v;
    pushdown(x);
    int mid = (st[x].l + st[x].r) / 2, res = 0;
    if (l <= mid) res += query(ls(x), l, r);
    if (r >  mid) res += query(rs(x), l, r);
    return res % p;
}

带暴力的线段树

考虑一种需要区间开根号的操作(向下取整)的线段树,怎么写?
对于一个int类型的数,至多开个10次就差不多变成1或者0了,而这两个数显然不管之后怎么开,值都不会变化。
那么,我们想到一种暴力的策略:正常情况下,每次操作的时候都直接进行比较暴力的修改(多次单点修改罢了),但是与此同时,我们给每个区间做一个标记,这个标记记录了该区间的值是否是全0或者全1,如果修改的时候碰到这种区间,那么就不操作,直接返回。
显然,每个数至多被开五六次平方,每个数被开方的复杂度都是线段树 - 图1 的,所以总复杂度在 线段树 - 图2 这样,并不会超时(K 是开方的次数,如上所示,是一个很小的常数)。
这种线段树又被称作势能线段树(因为不管局部操作的复杂度是否正确,但是整体复杂度不变),不仅可以处理开方,也可以用来处理其他的可以使得一个数快速收敛的操作,例如lowbit操作。

//P4145 上帝造题的七分钟 2 / 花神游历各国
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 100010;
struct tree {
    int l, r;
    LL sum;
    bool flag;
} a[N << 2];
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
void pushup(int x)
{
    a[x].sum = a[ls(x)].sum + a[rs(x)].sum;
    a[x].flag = (a[ls(x)].flag && a[rs(x)].flag);
}
void build(int x, int l, int r)
{
    a[x].l = l, a[x].r = r;
    if (l == r) {
        scanf("%lld", &a[x].sum);
        return;
    }
    int mid = (l + r) >> 1;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    pushup(x);
}
LL query(int x, int l, int r) {
    if (l <= a[x].l && a[x].r <= r)
        return a[x].sum;
    int mid = (a[x].l + a[x].r) >> 1;
    LL res = 0;
    if (l <= mid) res += query(ls(x), l, r);
    if (r >  mid) res += query(rs(x), l, r);
    return res;
}
void change(int x, int l, int r) {
    if (a[x].flag) return;
    if (a[x].l == a[x].r) {
        a[x].sum = sqrt(a[x].sum);
        a[x].flag = (a[x].sum == 0 || a[x].sum == 1);
        return;
    }
    int mid = (a[x].l + a[x].r) >> 1;
    if (l <= mid) change(ls(x), l, r);
    if (r >  mid) change(rs(x), l, r);
    pushup(x);
}