HDU - 4348 To the moon(主席树区间更新-标记永久化)

题目链接:点击查看

题目大意:给出一个初始时长度为 n 的序列,有 m 次操作,每种操作分为下列四种类型:

  1. C l r d:新建一个继承了前一个版本的数组,并将区间 [ l , r ] 内的数字都加上 d
  2. Q l r:访问最后一个版本的数组,[ l , r ] 内的区间和
  3. H l r t:访问第 t 个版本的数组,[ l , r ] 内的区间和
  4. B t:回溯到第 t 个版本

题目分析:如果不考虑变量 t ,也就是不同版本的数组所带来的影响,那么这就是线段树的一道经典区间修改和区间查询的题目,写一个 lazy 下传标记即可

考虑有多个不同版本的数组,也就对应了主席树,换句话说这个题目考察的正是主席树的区间修改和区间查询

主席树的单点修改比较简单,每次只会涉及到一条链,即 logn 条节点,直接新建,其余的节点继承上一个位置的即可

区间修改的话如果考虑 lazy 变量的下传,最坏的情况下需要从根节点下传到叶子节点,一次操作的时空复杂度都是 nlogn 级别的

那可能有的同学会说了:我就先存一下 lazy 标记,等查询的时候再下传不就行了?

很遗憾的说,这样的时间复杂度单次操作也将会是 nlogn 级别的,因为不同版本的线段树可能 lazy 标记不同,导致 nlogn 个节点的值最终都不相同,所以都需要新建才行

所以这里需要引入一个新的概念,也就是线段树的标记永久化

对于普通线段树的 lazy 标记下传操作,也就是 pushdown 函数,对应过去就是:线段树中每个节点的值都是真实的值

但标记永久化顾名思义,也就是 lazy 标记不下传,这样线段树中的每个节点对应的值此时并不是真实的值,但在执行 query 操作时,一定会从其父节点下来,此时额外维护一个变量,用于记录沿途路径上的 lazy 的贡献,最后统一计算一样可以得到真实答案

但在执行标记永久化时需要注意的细节就是,不能使用 pushup 函数自底向上去传递贡献,只能自顶向下去维护每个节点的贡献,到达需要返回的位置时,打一个 lazy 标记直接返回即可,这样的操作带来的影响是:相应的区间上面的父节点中储存的是真实值,而 lazy 标记下面的那些节点中存在的都是一个虚拟的值,需要与祖先节点中的 lazy 标记的贡献进行加和才能得到真实答案

使用标记永久化的一个好处就是,每次修改仍然是至多修改 logn 个节点,这样就能实现主席树的区间修改的操作了

对于这个题目而言,注意一下空间大小,因为初始时需要建立一棵线段树,这里需要 4 * n ,然后每次修改都需要新建 logn 条树链,设 n 与 m 同阶,最终需要新建 nlogn 个树链,总的空间复杂度也就是 n ( 4 + nlogn ),logn 记为 20,所以开 25 * n 就好了

代码:

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
     
typedef long long LL;
     
typedef unsigned long long ull;
     
const int inf=0x3f3f3f3f;

const int N=1e5+100;

struct Node
{
    int l,r;
    LL sum,lazy;
}tree[N*25];

int cnt,root[N];

void build(int &k,int l,int r)
{
    k=cnt++;
    tree[k].lazy=0;
    if(l==r)
    {
        scanf("%lld",&tree[k].sum);
        return;
    }
    int mid=l+r>>1;
    build(tree[k].l,l,mid);
    build(tree[k].r,mid+1,r);
    tree[k].sum=tree[tree[k].l].sum+tree[tree[k].r].sum;
}

void update(int &k,int l,int r,int L,int R,LL val)//[l,r]:目标区间 [L,R]:当前区间 
{
    tree[cnt++]=tree[k];
    k=cnt-1;
    tree[k].sum+=val*(r-l+1);
    if(L>=l&&R<=r)
    {
        tree[k].lazy+=val;
        return;
    }
    int mid=L+R>>1;
    if(r<=mid)
        update(tree[k].l,l,r,L,mid,val);
    else if(l>mid)
        update(tree[k].r,l,r,mid+1,R,val);
    else
    {
        update(tree[k].l,l,mid,L,mid,val);
        update(tree[k].r,mid+1,r,mid+1,R,val);
    }
}

LL query(int k,int l,int r,int L,int R,LL add)//[l,r]:目标区间 [L,R]:当前区间 
{
    if(R<l||L>r)
        return 0;
    if(L>=l&&R<=r)
        return tree[k].sum+add*(R-L+1);
    int mid=L+R>>1;
    return query(tree[k].l,l,r,L,mid,add+tree[k].lazy)+query(tree[k].r,l,r,mid+1,R,add+tree[k].lazy);
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        cnt=1;
        build(root[0],1,n);
        int time=0;
        while(m--)
        {
            char op[5];
            scanf("%s",op);
            if(op[0]=='C')
            {
                int l,r,d;
                scanf("%d%d%d",&l,&r,&d);
                time++;
                root[time]=root[time-1];
                update(root[time],l,r,1,n,d);
            }
            else if(op[0]=='Q')
            {
                int l,r;
                scanf("%d%d",&l,&r);
                printf("%lld\n",query(root[time],l,r,1,n,0));
            }
            else if(op[0]=='H')
            {
                int l,r,t;
                scanf("%d%d%d",&l,&r,&t);
                printf("%lld\n",query(root[t],l,r,1,n,0));
            }
            else if(op[0]=='B')
            {
                scanf("%d",&time);
            }
        }
    }





    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/109091713