[bzoj 3064] Tyvj 1518 CPU监控:线段树的Lazy tag

题意:给定n个初始值和一组对区间的操作操作,要求对操作应答。操作包括:1. 修改为指定数。2. 增加一个数。3. 询问区间最大值。4. 询问区间的历史最大值。

《NOI导刊》的培训上,zrt神犇推荐了这道题,说可以加深对lazy tag的理解。乍一看,这不是水题??WA……

网上题解说这是水题,可是我搞了好几天。学习了一下题解,愈发不解。也许还是得自己领悟。在火车上头疼了一天,用光了笔记本的电。有收获。

记修改为=,累加为+,则所有操作序列可化为 ++…+==…=的形式,即先进行0个或以上累加操作,再进行0个或以上赋值操作。

如:+4 +5 =2 +(-6) +7 =1 可化为:+4 +5 =2 =-4 =3 =1。

所以,我们用三个lazy tag刻画一组操作:a(累加)和s(赋值),以及和s配套的fs(是否赋值)。若fs=true,以s为准(a会被设为0);否则以a为准。向下传递累加操作的过程中,若孩子的fs=true,累加到s上,否则累加到a上。

从最终的效果来看,上面的例子可再化简为 =1,然而这样会丢失历史信息。向上面那样,保留“一帧帧”操作,便于处理历史最值。

用两个lazy tag刻画历史:ah(历史最大累加)和sh(历史最大赋值)。这里就不再另设赋值与否的标记了。因为,作为历史,即最大值的最大值,赋为-inf是有意义的。前面其实也可以这样,但是意义略微有些不明朗。在上面的例子中,ah=9,sh=3。之所以要记录两个,是因为缺乏加法的另一个操作数,无从确定结果究竟哪个大。如:设原数为0,则累加更大;设原数为-7,则赋值更大。而区间中的数是各式各样的。

来看看两个操作序列的历史如何复合(pushdown时怎样合并)。

  1. ++…+==…= x ++…+==…=
    ah = ah1
    sh = max(sh1, s1+ah2, sh2)

  2. ++…+ x ++…+==…=
    ah = max(ah1, a1+ah2)
    sh = sh2

不用进行更多的讨论了,因为add标记有0,set标记有-inf,可以屏蔽自身。需要注意的是,mx是执行了操作序列1后的结果,ah和它并不配套。

添加新标记,也可视为复合。

总结

可以归纳出设计lazy tag的步骤:
1. 用哪些量刻画一组操作?
2. 两个操作序列的结合性是怎样的?
3. 标记的优先级是怎样的?
4. 怎样快速得到操作的等价效果?

误区

  1. 不加额外的历史标记。这样能过样例……样例看起来很丰富,实际上很水。
  2. 使赋值的优先级高于累加。会丢失信息。
  3. 实时标记和历史标记的赋值、累加优先级不一致。不问历史,怎么规定优先级都可以。刘汝佳《算法竞赛入门经典2》里的规定就和这里不同,刚开始我生搬硬套了。隐约意识到问题,却只改了历史标记的优先级。
  4. 没抓住历史和实时之间的联系。一个很重要的联系是,它们始终是同时传递的。

老毛病了。没搞明白还想A题?
贺神说:慢慢来。
虽然上下文不同,但在这里也是适用的。
我太想AC了。对拍的时候都是心惊胆颤的。

Code

#include <cstdio>
#include <algorithm>
#include <cctype>

using namespace std;
const int MAXT = 100000, NEG_INF = 0x80000000;
int A[MAXT+1];

struct Node {
    int mx, mxh, a, ah, s, sh; // 最大值,历史最大值,累加标记,历史累加标记,历史赋值的值
    bool fs; // 赋值标记
} T[MAXT*4];

inline void update(int& x, int y)
{
    x = max(x, y);
}

inline void maintain(int o)
{
    T[o].mx = max(T[o*2].mx, T[o*2+1].mx);
    T[o].mxh = max(T[o*2].mxh, T[o*2+1].mxh);
}

inline void push_his(Node& self, Node& fa)
{
    update(self.mxh, max(self.mx + fa.ah, fa.sh));
    if (self.fs) // +++=== x +++===
        update(self.sh, max(self.s + fa.ah, fa.sh));
    else { // +++ x +++===
        update(self.ah, self.a + fa.ah);
        self.sh = fa.sh;
    }
}

inline void push_now(Node& self, Node& fa)
{
    if (fa.fs) {
        self.fs = true;
        self.mx = self.s = fa.s;
        self.a = 0;
    } else {
        (self.fs ? self.s : self.a) += fa.a;
        self.mx += fa.a;
    }
}

inline void pushdown(int o)
{
    Node& lc = T[o*2], & rc = T[o*2+1], & self = T[o];
    push_his(lc, self);
    push_his(rc, self);
    push_now(lc, self);
    push_now(rc, self);
    self.fs = false;
    self.sh = NEG_INF;
    self.a = self.ah = 0;
}

void build(int o, int l, int r)
{
    T[o].sh = NEG_INF;
    if (l == r) {
        T[o].mx = T[o].mxh = A[l];
        return;
    }
    int m = (l+r)/2;
    build(o*2, l, m);
    build(o*2+1, m+1, r);
    maintain(o);
}

void modify_add(int o, int l, int r, int L, int R, int a)
{
    if (L<=l && r<=R) {
        Node x = (Node){0, 0, a, a, 0, NEG_INF, false};
        push_his(T[o], x);
        push_now(T[o], x);
        return;
    }
    pushdown(o);
    int m = (l+r)/2;
    if (L <= m)
        modify_add(o*2, l, m, L, R, a);
    if (R > m)
        modify_add(o*2+1, m+1, r, L, R, a);
    maintain(o);
}

void modify_set(int o, int l, int r, int L, int R, int v)
{
    if (L<=l && r<=R) {
        Node x = (Node){0, 0, 0, 0, v, v, true};
        push_his(T[o], x);
        push_now(T[o], x);
        return;
    }
    pushdown(o);
    int m = (l+r)/2;
    if (L <= m)
        modify_set(o*2, l, m, L, R, v);
    if (R > m)
        modify_set(o*2+1, m+1, r, L, R, v);
    maintain(o);
}

void query(int o, int l, int r, int L, int R, int& now, int& his)
{
    if (L<=l && r<=R) {
        now = max(now, T[o].mx);
        his = max(his, T[o].mxh);
        return;
    }
    pushdown(o);
    int m = (l+r)/2;
    if (L <= m)
        query(o*2, l, m, L, R, now, his);
    if (R > m)
        query(o*2+1, m+1, r, L, R, now, his);
}

int query(int n, int L, int R, int op)
{
    int now = NEG_INF, his = NEG_INF;
    query(1, 1, n, L, R, now, his);
    return op ? his : now;
}

inline int read()
{
    int s = 1, x = 0; char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') s = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x*10 + ch - '0';
        ch = getchar();
    }
    return s*x;
}

int main()
{
    int t = read();
    for (int i = 1; i <= t; ++i)
        A[i] = read();
    build(1, 1, t);

    int E = read();

    while (E--) {
        char op = getchar();
        while (!isalpha(op))
            op = getchar();
        int x = read(), y = read(), z;
        switch (op) {
            case 'Q':
                printf("%d\n", query(t, x, y, 0));
                break;
            case 'A':
                printf("%d\n", query(t, x, y, 1));
                break;
            case 'P':
                z = read();
                modify_add(1, 1, t, x, y, z);
                break;
            case 'C':
                z = read();
                modify_set(1, 1, t, x, y, z);
        }
    }
    return 0;
}

到成都了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值