对线段树的理解

       线段树是一棵二叉树,每个节点存储了区间的左端点,右端点,和对应区间上的某个数据(如果使用延迟更新,还有相应的懒惰标记)

维护区间和的线段树(更新操作只有区间加)

洛谷P3372

#include <bits/stdc++.h>
#define MAXN 100005
#define LEFT(i) ((i) << 1)
#define RIGHT(i) ((i) << 1 | 1)
using namespace std;
struct Node {
    int l;
    int r;
    long long data;
    long long lz;
};
Node tree[4 * MAXN];
int input[MAXN];
void pushdown(int i) {
    if (tree[i].lz) {
        tree[LEFT(i)].lz += tree[i].lz;
        tree[RIGHT(i)].lz += tree[i].lz;
        tree[LEFT(i)].data +=
            tree[i].lz * (tree[LEFT(i)].r - tree[LEFT(i)].l + 1);
        tree[RIGHT(i)].data +=
            tree[i].lz * (tree[RIGHT(i)].r - tree[RIGHT(i)].l + 1);
        tree[i].lz = 0;
    }
}
void build(int i, int l, int r) {
    tree[i].l = l, tree[i].r = r;
    if (l == r) {
        tree[i].data = input[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(LEFT(i), l, mid);
    build(RIGHT(i), mid + 1, r);
    tree[i].data = tree[LEFT(i)].data + tree[RIGHT(i)].data;
}
long long search(int i, int l, int r) {
    if (l <= tree[i].l && r >= tree[i].r)
        return tree[i].data;
    if (tree[i].l > r || tree[i].r < l)
        return 0;
    pushdown(i);
    long long s = 0;
    if (tree[LEFT(i)].r >= l)
        s += search(LEFT(i), l, r);
    if (tree[RIGHT(i)].l <= r)
        s += search(RIGHT(i), l, r);
    return s;
}
void add(int i, int l, int r, long long k) {
    if (l <= tree[i].l && r >= tree[i].r) {
        tree[i].data += k * (tree[i].r - tree[i].l + 1);
        tree[i].lz += k;
        return;
    }
    if (tree[i].l > r || tree[i].r < l)
        return;
    pushdown(i);
    if (tree[LEFT(i)].r >= l)
        add(LEFT(i), l, r, k);
    if (tree[RIGHT(i)].l <= r)
        add(RIGHT(i), l, r, k);
    tree[i].data = tree[LEFT(i)].data + tree[RIGHT(i)].data;
}
/*void dfs(int i) {
    if (tree[i].l == tree[i].r) {
        printf("%d ", tree[i].data);
        return;
    }
    pushdown(i);
    dfs(LEFT(i));
    dfs(RIGHT(i));
}*/
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%d", &input[i]);
    build(1, 0, n - 1);
    while (m--) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        l--, r--;
        if (opt == 1) {
            long long k;
            scanf("%ld", &k);
            add(1, l, r, k);
        } else if (opt == 2) {
            printf("%ld\n", search(1, l, r));
        }
    }
    return 0;
}
维护区间和的线段树(区间更新操作有区间加和区间乘)

洛谷P3373

#include <bits/stdc++.h>
#define MAXN 100005
#define LEFT(i) ((i) << 1)
#define RIGHT(i) ((i) << 1 | 1)
using namespace std;
struct Node {
    int l;
    int r;
    long long data;
    long long plz;
    long long mlz;
};
Node tree[4 * MAXN];
int input[MAXN];
int p;
void pushdown(int i) {
    long long k1 = tree[i].mlz, k2 = tree[i].plz;
    tree[LEFT(i)].data = (k1 * tree[LEFT(i)].data +
                          k2 * (tree[LEFT(i)].r - tree[LEFT(i)].l + 1)) %
                         p;
    tree[RIGHT(i)].data = (k1 * tree[RIGHT(i)].data +
                           k2 * (tree[RIGHT(i)].r - tree[RIGHT(i)].l + 1)) %
                          p;
    tree[LEFT(i)].mlz = (tree[LEFT(i)].mlz * k1) % p;
    tree[RIGHT(i)].mlz = (tree[RIGHT(i)].mlz * k1) % p;
    tree[LEFT(i)].plz = (k1 * tree[LEFT(i)].plz + k2) % p;
    tree[RIGHT(i)].plz = (k1 * tree[RIGHT(i)].plz + k2) % p;
    tree[i].plz = 0;
    tree[i].mlz = 1;
}
void build(int i, int l, int r) {
    tree[i].l = l, tree[i].r = r;
    tree[i].mlz = 1, tree[i].plz = 0;
    if (l == r) {
        tree[i].data = input[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(LEFT(i), l, mid);
    build(RIGHT(i), mid + 1, r);
    tree[i].data = (tree[LEFT(i)].data + tree[RIGHT(i)].data) % p;
}
long long search(int i, int l, int r) {
    if (l <= tree[i].l && r >= tree[i].r)
        return tree[i].data;
    if (tree[i].l > r || tree[i].r < l)
        return 0;
    pushdown(i);
    long long s = 0;
    if (tree[LEFT(i)].r >= l)
        s += search(LEFT(i), l, r) % p;
    if (tree[RIGHT(i)].l <= r)
        s += search(RIGHT(i), l, r) % p;
    return s % p;
}
void add(int i, int l, int r, long long k) {
    if (l <= tree[i].l && r >= tree[i].r) {
        tree[i].data = (tree[i].data + k * (tree[i].r - tree[i].l + 1)) % p;
        tree[i].plz = (tree[i].plz + k) % p;
        return;
    }
    if (tree[i].l > r || tree[i].r < l)
        return;
    pushdown(i);
    if (tree[LEFT(i)].r >= l)
        add(LEFT(i), l, r, k);
    if (tree[RIGHT(i)].l <= r)
        add(RIGHT(i), l, r, k);
    tree[i].data = (tree[LEFT(i)].data + tree[RIGHT(i)].data) % p;
}
void mul(int i, int l, int r, long long k) {
    if (l <= tree[i].l && r >= tree[i].r) {
        tree[i].data = (tree[i].data * k) % p;
        tree[i].mlz = (tree[i].mlz * k) % p;
        tree[i].plz = (tree[i].plz * k) % p;
        return;
    }
    if (tree[i].l > r || tree[i].r < l)
        return;
    pushdown(i);
    if (tree[LEFT(i)].r >= l)
        mul(LEFT(i), l, r, k);
    if (tree[RIGHT(i)].l <= r)
        mul(RIGHT(i), l, r, k);
    tree[i].data = (tree[LEFT(i)].data + tree[RIGHT(i)].data) % p;
}
int main() {
    int n, m;
    scanf("%d%d%d", &n, &m, &p);
    for (int i = 0; i < n; i++)
        scanf("%d", &input[i]);
    build(1, 0, n - 1);
    while (m--) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        l--, r--;
        if (opt == 1) {
            long long k;
            scanf("%ld", &k);
            mul(1, l, r, k);
        } else if (opt == 2) {
            long long k;
            scanf("%ld", &k);
            add(1, l, r, k);
        } else if (opt == 3) {
            printf("%ld\n", search(1, l, r));
        }
    }
    return 0;
}
懒惰标记的理解

       对于有多种更新操作的线段树,关键是lazytag(懒惰标记)的处理,无非是操作的先后顺序不同。
       假设操作1把 x x x经过操作数 k k k映射到了 f ( x , k ) f(x,k) f(x,k),操作2把 x x x经过操作数 k k k映射到了 g ( x , k ) g(x,k) g(x,k)。此时两种对应的懒标记名称为 f l z flz flz g l z glz glz。如果秉承先处理操作 f f f后处理操作 g g g的原则,原来节点的值 x x x就变成了 g ( f ( x , f l z ) , g l z ) g(f(x,flz),glz) g(f(x,flz),glz)。现在它的父节点传来一组标记 f l z ′ flz' flz g l z ′ glz' glz
那么要对当前节点继续更新,节点的值 g ( f ( x , f l z ) , g l z ) g(f(x,flz),glz) g(f(x,flz),glz)就应该变成 g ( f ( g ( f ( x , f l z ) , g l z ) , f l z ′ ) , g l z ′ ) g(f(g(f(x,flz),glz),flz'),glz') g(f(g(f(x,flz),glz),flz),glz)
       此时如何进行懒标记的更新呢?
       那就要想办法将 g ( f ( g ( f ( x , f l z ) , g l z ) , f l z ′ ) , g l z ′ ) g(f(g(f(x,flz),glz),flz'),glz') g(f(g(f(x,flz),glz),flz),glz)化成 g ( f ( x , F ) , G ) g(f(x,F),G) g(f(x,F),G)的形式。此时,只需要将 f l z flz flz更新为 F F F,将 g l z glz glz更新成 G G G即可。

以具体例子为例:
        f ( x , k ) = k x f(x,k)=kx f(x,k)=kx
        g ( x , k ) = x + k g(x,k)=x+k g(x,k)=x+k
        g ( f ( x , f l z ) , g l z ) = f l z ⋅ x + g l z g(f(x,flz),glz)=flz\cdot x+glz g(f(x,flz),glz)=flzx+glz
此时    g ( f ( g ( f ( x , f l z ) , g l z ) , f l z ′ ) , g l z ′ ) g(f(g(f(x,flz),glz),flz'),glz') g(f(g(f(x,flz),glz),flz),glz)
        = f l z ′ ( f l z ⋅ x + g l z ) + g l z ′ =flz'(flz\cdot x+glz)+glz' =flz(flzx+glz)+glz
        = f l z ′ ⋅ f l z ⋅ x + f l z ′ ⋅ g l z + g l z ′ =flz'\cdot flz\cdot x+flz'\cdot glz+glz' =flzflzx+flzglz+glz
        = g ( f ( x , f l z ′ ⋅ f l z ) , f l z ′ ⋅ g l z + g l z ′ ) =g(f(x,flz'\cdot flz),flz'\cdot glz+glz') =g(f(x,flzflz),flzglz+glz)
所以,
        F = f l z ′ ⋅ f l z F=flz'\cdot flz F=flzflz
        G = f l z ′ ⋅ g l z + g l z ′ G=flz'\cdot glz+glz' G=flzglz+glz

在第二个例子中,对应的pushdown更新模式也是这样的

void pushdown(int i) {
    long long k1 = tree[i].mlz, k2 = tree[i].plz;
    tree[LEFT(i)].data = (k1 * tree[LEFT(i)].data +
                          k2 * (tree[LEFT(i)].r - tree[LEFT(i)].l + 1)) %
                         p;
    tree[RIGHT(i)].data = (k1 * tree[RIGHT(i)].data +
                           k2 * (tree[RIGHT(i)].r - tree[RIGHT(i)].l + 1)) %
                          p;
    tree[LEFT(i)].mlz = (tree[LEFT(i)].mlz * k1) % p;
    tree[RIGHT(i)].mlz = (tree[RIGHT(i)].mlz * k1) % p;
    tree[LEFT(i)].plz = (k1 * tree[LEFT(i)].plz + k2) % p;
    tree[RIGHT(i)].plz = (k1 * tree[RIGHT(i)].plz + k2) % p;
    tree[i].plz = 0;
    tree[i].mlz = 1;
}

       在加法和乘法的过程中,也需要对自身的lazytag进行更新,而且原则要和pushdown的原则一致。

k k k
       相当于某个神秘力量传来一组懒惰标记 ( f l z ′ , g l z ′ ) = ( 1 , k ) (flz',glz')=(1,k) (flz,glz)=(1,k)
代入
        F = f l z ′ ⋅ f l z F=flz'\cdot flz F=flzflz
        G = f l z ′ ⋅ g l z + g l z ′ G=flz'\cdot glz+glz' G=flzglz+glz
懒标记更新方式为:
        F = f l z F=flz F=flz
        G = g l z + k G=glz+k G=glz+k
对应代码:

void add(int i, int l, int r, long long k) {
    if (l <= tree[i].l && r >= tree[i].r) {
        tree[i].data = (tree[i].data + k * (tree[i].r - tree[i].l + 1)) % p;
        tree[i].plz = (tree[i].plz + k) % p;
        return;
    }
    if (tree[i].l > r || tree[i].r < l)
        return;
    pushdown(i);
    if (tree[LEFT(i)].r >= l)
        add(LEFT(i), l, r, k);
    if (tree[RIGHT(i)].l <= r)
        add(RIGHT(i), l, r, k);
    tree[i].data = (tree[LEFT(i)].data + tree[RIGHT(i)].data) % p;
}

k k k
       相当于某个神秘力量传来一组懒惰标记 ( f l z ′ , g l z ′ ) = ( k , 0 ) (flz',glz')=(k,0) (flz,glz)=(k,0)
代入
        F = f l z ′ ⋅ f l z F=flz'\cdot flz F=flzflz
        G = f l z ′ ⋅ g l z + g l z ′ G=flz'\cdot glz+glz' G=flzglz+glz
懒标记更新方式为:
        F = k ⋅ f l z F=k\cdot flz F=kflz
        G = k ⋅ g l z G=k\cdot glz G=kglz
对应代码:

void mul(int i, int l, int r, long long k) {
    if (l <= tree[i].l && r >= tree[i].r) {
        tree[i].data = (tree[i].data * k) % p;
        tree[i].mlz = (tree[i].mlz * k) % p;
        tree[i].plz = (tree[i].plz * k) % p;
        return;
    }
    if (tree[i].l > r || tree[i].r < l)
        return;
    pushdown(i);
    if (tree[LEFT(i)].r >= l)
        mul(LEFT(i), l, r, k);
    if (tree[RIGHT(i)].l <= r)
        mul(RIGHT(i), l, r, k);
    tree[i].data = (tree[LEFT(i)].data + tree[RIGHT(i)].data) % p;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值