• 可持久化线段树区间查询 + 永久化标记 T^T online judge 2507


    AC的故事大结局悲剧版(下)

    TimeLimit:4000MS  MemoryLimit:258MB
    64-bit integer IO format:%lld
    未解决 | 点击收藏
    Problem Description

            事情进展得很顺利,测试修正后的ACRobot所向披靡,挂起不到一天,便从互联网的茫茫大海中搜寻到了小C的踪迹,小C被关押在神秘组织的大本营,于是小A便赶紧展开营救行动。

      小A发现,神秘组织在基地外围布置了n 个营地,编号从1 - n,每个营地都有大量的兵力,小A完全无法进入,不过在潜伏一周以后小A发现每个营地的士兵数量天天都在变动,所以小A决定写个软件来统计敌方兵力情况。

      程序具体如下:

         在t=0天时每个营地有Ai名士兵。

        现有m个操作,分三类:

        A:l 到r 营地在新的一天里新增士兵k名(随后时间从t 现在变成t+1了)

        B:问第i天 l 到r 营地共有士兵几名。

        C:时间倒流到x天 。(厉害了。。。)

       小A只负责深入重围,抱得美人归和撒狗粮,于是就把这个计算的任务就交给了在座的各位。

      (1号结局,本场有人AC了这题:经过计算终于得出结论,发现营地真是守卫森严,根本没有办法攻入,小A冒死进去救人,被乱刀砍死,小C见状也从敌人大本营100楼跳下。后来有人说当时看到两只蝴蝶,一只从大本营楼上飞下来,一只从地上飞上去。)

      (2号结局,本场没有人AC这题:五个小时过去了,小C长时间在绝望中度过,于是便动了轻生的念头,终于她找个一个合适的时机,一头撞向石柱,那一刻,小A也心有所感,拿出深藏许久的宝刀引颈自戮,那一霎那,天空突然阴云密布,不一会被下起了倾盆大雨。后来有人说当时看到了两只蝴蝶颤颤巍巍地飞到了一起,又一起飞走了。)

        虽然小A没有救出小C,但江湖中人感叹小A和小C的感情,为他们起了个名字AC自动机,用来祭奠他们,而小A当初写的爬虫ACRobot无休无止地爬在互联网上,苦寻自家主人却无果。

    Input

    有多组样例,每组样例首先是一个n 表示营地数量。 1<=n<=10万

    接下来n 个整数表示每个营地的初始人数。

    接下来一个m 表示操作数目。1<=m<=10万

    接下来分A、B、C三类操作,格式如下:

    A l r k,1<=l<=r<=n, k为整数

    B t l r,0<=t<=当前时间, 1<=l<=r<=n

    C x,0<=x<=当前时间

    题目中的整数范围是[-2^23, 2^23-1]

    Output

    每组测试样例首先输出样例编号。

    对于B 操作,给出人数。

    参见样例。

    SampleInput
    3
    1 2 3
    3
    B 0 1 3
    A 1 2 2
    B 1 1 3
    3
    1 2 3
    3
    B 0 1 3
    A 1 2 2
    B 0 1 3
    
    SampleOutput
    Case 1:
    6
    10
    Case 2:
    6
    6
    
    题目统计信息详细
    总AC数 12
    通过人数 7
    尝试人数 15
    总提交量 146
    AC率 4.79%
    AC该题后可以添加标签
    贴完标签可以获得20ACB。
    并且可以获得本题所有提交代码查看权限。
    点击标题可以显示标签。
    如果你还没认真思考过这题,请不要查看标签
    如果您已经通过了该题,请务为该题贴上标签
    作者
     
     
    【思路】: 
      第一感觉是一棵主席树,涉及主席树的区间更新,一开始的想法就是对每棵线段树进行lazy标记
    并且向下更新,这样的话,遇到极端数据,即一次更新n个节点,那么就等于每次都更新一棵树,这样
    的话后果就是根本存不下
      那么我们有什么办法可以维护这棵主席树呢,依照我们单点更新主席树的想法就是尽量用上前面的节点,
    那么我们就要引入一个东西,即标记永久化。
      什么是标记永久化,在我的理解,就是标记lazy但是不下压。
      如图,
     

    假设更新(1,3)那么先给(1,4).sum+= val * (4 - 1 + 1),

    然后向下遍历,给(1,2)打上标记,然后lazy不下压,这个时候(1,2).sum += val *(2 - 1 + 1) 

    给(1,2).lazy += val;假设我要查询的是(1,1)那么我们在遍历的时候,声明一个变量sums,来存储需要

    更新的lazy值,因为上一次我们更新停止在了(1,2),然后我们遍历到(1,2)的时候sums += (1,2).lazy

    然后往下查询,查询到了我们想要的(1,1),那么就(1,1).sum += sums * (1 - 1 + 1);

    这样就可以给出答案了

    贴上代码:

    (代码有注释)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int MAXN = 1e5 + 5;
    typedef long long ll;
    typedef struct SEGTREE
    {
        ll l, r, ls, rs, sum, lazy;
    }segtree;
    segtree seg[MAXN * 20];
    ll arr[MAXN];
    ll root[MAXN];
    ll tot = 0;
    ll build(ll now, ll l, ll r)
    {
        ll p = ++ tot;
        seg[p].lazy = 0;
        seg[p].sum = 0;
        seg[p].ls = l;
        seg[p].rs = r;
        if(l == r)
        {
            seg[p].sum = arr[l];
            return p;
        }
        ll mid = (l + r) >> 1;
        seg[p].l = build(seg[p].l, l, mid);
        seg[p].r = build(seg[p].r, mid + 1, r);
        seg[p].sum = seg[seg[p].l].sum + seg[seg[p].r].sum;
        return p;
    }
    ll update(ll l, ll r, ll now, ll val)
    {
        ll p = ++ tot;
        seg[p] = seg[now];
        seg[p].sum += (r - l + 1) * val;
        ///更新包含了update范围的节点
        if(seg[p].ls == l && seg[p].rs == r)
        {
            seg[p].lazy += val;
            ///[l,r]如果是匹配的话,那么更新lazy的值
            ///不往下更新,这样的话标记永久化后,就可以节省空间
            ///不用更新一次就多出一颗线段树
            ///这样的话,在下次查询的时候,查询范围小于最后这个节点的范围
            ///只要记录lazy值然后向下查询到底,进行更新就正确了
            return p;
        }
        ll mid = (seg[p].ls + seg[p].rs) / 2;
        if(r <= mid)
        {
            seg[p].l = update(l, r, seg[p].l, val);
        }
        else if(mid < l)
        {
            seg[p].r = update(l, r, seg[p].r, val);
        }
        else
        {
            seg[p].l = update(l, mid, seg[p].l, val);
            seg[p].r = update(mid + 1, r, seg[p].r, val);
        }
        return p;
    }
    ll query(ll now, ll l, ll r, ll lazy)
    ///lazy 是记载我要查询的这个区间是如何变化的,
    ///因为lazy只标记在要更新的区间,lazy是不下压的
    ///那么我每次查询的时候只要把lazy经过的区间的lazy进行记载
    ///然后向下累计,直到要查询的区间,在添加上去,就完成了标记永久化
    ///或许你会不理解为什么是这样操作的
    ///因为在前面我们进行update的时候,把对应的[l,r]更新了,但是比[l,r]更小的范围是没有
    ///进行更新的,那么我们只要记录从父节点向下更新到我们要查询的节点,所增加的lazy的值
    ///就可以得到查询区间更新的值,即lazy * (r - l + 1)
    ///或许你会说,我们不是更新到前面操作的更新区间的[l,r],就会改变lazy值吗?那么再加为什么不会
    ///发生错误,其实很简单,更新区间的[l,r]不向下更新,我们如果查询的依旧是[l,r],那么到了l,r就结束了
    ///是不会再向下的,所以是没有影响的
    {
        ll p = now;
        if(seg[p].ls == l && seg[p].rs == r)
        {
            return seg[p].sum + lazy * (r - l + 1);
        }
        ll mid = (seg[p].ls + seg[p].rs) / 2;
        if(r <= mid)
        {
            return query(seg[p].l, l, r, lazy + seg[p].lazy);
        }
        else if(l > mid)
        {
            return query(seg[p].r, l, r, lazy + seg[p].lazy);
        }
        else
        {
            return query(seg[p].l, l, mid, lazy + seg[p].lazy) + query(seg[p].r, mid + 1, r, lazy + seg[p].lazy);
        }
    }
    void change(ll ans)
    ///这里有个坑点,即如果ans == day,那么root[ans + 1] = 0, 0 - 1 = - 1
    ///那么就re了,emmmmm
    ///所以特判下==day就ok了
    {
        tot = root[ans + 1] - 1;
    }
    int main()
    {
        int n, m, flag;
        flag = 0;
        while(~scanf("%d",&n))
        {
            tot = 0;
            for (int i = 1; i <= n; i ++) {
                scanf("%lld",&arr[i]);
            }
            root[0] = build(tot, 1, n);
            printf("Case %d:
    ",++ flag);
            ll day = 0;
            scanf("%d",&m);
            for (int i = 0; i < m; i ++) {
                char temp;
                cin >> temp;
                if(temp == 'A')
                {
                    ll l, r, val;
                    scanf("%lld%lld%lld", &l, &r, &val);
                    root[day + 1] = update(l, r, root[day], val);
                    day ++;
                }
                else if(temp == 'B')
                {
                    ll l, r, t;
                    scanf("%lld%lld%lld", &t, &l, &r);
                    ll re = query(root[t], l, r, 0);
                    printf("%lld
    ",re);
                }
                else
                {
                    ll ans;
                    scanf("%lld",&ans);
                    if(ans < day)
                    change(ans);
                    day = ans;
                }
            }
        }
        return 0;
    }

                

  • 相关阅读:
    【转】extern "C"的含义和用法
    python的shelve库
    【转】TCP是流传输协议,UDP是包传输协议
    【转】TCP慢启动、拥塞避免、快速重传、快速恢复
    【转】C/C++多线程编程中什么情况下需要加volatile?
    【转】C++对象是创建在堆上,还是在栈上?
    【转】腾讯高级工程师:一道面试题引发的高并发性能调试思考
    【转】C++类的sizeof大小
    【转】C编译器内存对齐
    【转】c++中使用memset初始化类对象
  • 原文地址:https://www.cnblogs.com/qq136155330/p/9682941.html
Copyright © 2020-2023  润新知