HDU4348 To the moon(主席树区间修改模板)

前言

题目链接

此题可以作为可持久化线段树区间修改的模板题,于罗勇军老师的《算法竞赛上册》4.4 可持久化线段树的区间更新部分被简单提及了一嘴 ------ 说是按标准主席树编码即可,然而并没有给出具体做法,\(\sout{可能是不想限制大家的想法吧}\)。

但实际上也并不是直接按照标准主席树编码那么简单,这种需要可持久化的区间操作引入了一个新的操作 ------ 标记永久化,话不多说,直接进入主题。

题解

简化题意

给定一个长度为 \(n\) 的序列 \(a\) 以及 \(m\) 个操作,操作有如下四种:

  1. \(C\),\(R\),\(L\),\(d\):给 \(L,R\) 内的每个数加上 \(d\),并将时间 \(T\) 加 \(1\)。
  2. \(Q\),\(L\),\(R\):查询当前 \(L,R\) 内所有数的和。
  3. \(H\),\(L\),\(R\),\(t\):查询 \(t\) 时,\(L,R\) 内所有数的和。
  4. \(B\),\(t\):返回时间 \(t\)。

其中,当前时间 \(T\) 初始为 \(1\);只有操作 \(1\) 才会使 \(T\) 增加;如果选择返回时间 \(t\),则 \(t\) 之后的操作全部作废。

思路

当读到查询历史时间的区间和时,会发现这题需要可持久化,并且 \(T\) 就是每个树根的编号。

那么考虑如何实现区间修改。

首先,区间操作为了防止爆时间,懒标记肯定时需要的。但如果像普通线段树一样,每次访问到一个结点就下传标记,那么会使得历史结点的值被改变。

怎么办呢?

方法一

容易想到的一种方案是:

每次下传的时候创建两个子结点,继承上一个版本的信息后再下传懒标记。显然,这一方案十分的正确,然而这题仅给了 64M 的空间,把这个单次操作产生的空间约为 \(O(2\times \log(N))\) 的方案 pass 掉了。

所以得考虑另一种方法,就是 标记永久化

方法二 - 标记永久化

什么是标记永久化?就是字面意思,将懒标记打完以后就放在那个结点再也不动了,会被新版本继承,但不会再被下传,这样就避免了更改历史结点的问题。

然后就是如何利用标记永久化求和:

这样想,如果直接询问根节点,那么子结点的标记肯定没法计算,所以上传操作 (即 updata) 仍然需要;如果有一个结点存在标记,那么它的整棵子树都要增加数值,若询问的区间在它子树内,而标记又不能下传,难以直接计算,所以开一个变量 Sum_Tag 来记录经过该结点时增量,通过 Sum_Tag 统计从根一路到达目标区间的增量。

如此一来,单次操作产生的空间约为 \(O(\log(N))\),在满足空间需求的条件下实现了区间修改和查询。

代码

思路讲完了,更具体的细节见代码:

cpp 复制代码
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,T;
ll a[N];
int tid,rt[N];
struct Tree{
    int ls,rs,tag;
    ll sum;
    Tree() {ls=rs=sum=tag=0;}
}tr[N*25];
#define mid ((l+r)>>1)
void updata(int p,int l,int r){
    int ls=tr[p].ls;
    int rs=tr[p].rs;
    tr[p].sum=tr[ls].sum+tr[rs].sum+(r-l+1)*tr[p].tag;//加上自身区间的增量
}
void build(int &p,int l,int r){
    p=++tid;
    if(l==r){
        tr[p].sum=a[l];
        return ;
    }
    build(tr[p].ls,l,mid);
    build(tr[p].rs,mid+1,r);
    updata(p,l,r);
}
void modify(int lp,int &p,int l,int r,int x,int y,ll v){
    p=++tid;
    tr[p]=tr[lp];
    if(x<=l&&r<=y){
        tr[p].tag+=v;
        tr[p].sum+=(r-l+1)*v;
        return ;
    }
    if(x<=mid) modify(tr[lp].ls,tr[p].ls,l,mid,x,y,v);
    if(mid<y) modify(tr[lp].rs,tr[p].rs,mid+1,r,x,y,v);
    updata(p,l,r);
}
ll query(int p,int l,int r,int x,int y,ll Sum_Tag){//通过Sum_Tag累计从根到目标区间的所有增量
    if(x<=l&&r<=y) return tr[p].sum+Sum_Tag*(r-l+1);//添加累计增量
    ll ans=0;
    //进入子区间时才给Sum_Tag增加自身的tag
    if(x<=mid) ans+=query(tr[p].ls,l,mid,x,y,Sum_Tag+tr[p].tag);
    if(mid<y) ans+=query(tr[p].rs,mid+1,r,x,y,Sum_Tag+tr[p].tag);
    return ans;
}
#undef mid
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        T=tid=0;
        build(rt[T],1,n);
        char op;
        int l,r,d,t;
        for(int i=1;i<=m;i++){
            cin>>op;
            if(op=='C'){
                scanf("%d%d%d",&l,&r,&d);
                modify(rt[T],rt[T+1],1,n,l,r,d);
                T++;
            }
            else if(op=='Q'){
                scanf("%d%d",&l,&r);
                printf("%lld\n",query(rt[T],1,n,l,r,0));
            }
            else if(op=='H'){
                scanf("%d%d%d",&l,&r,&t);
                printf("%lld\n",query(rt[t],1,n,l,r,0));
            }
            else if(op=='B'){
                scanf("%d",&t);
                T=t;
            }
        }
    }

    return 0;
}
相关推荐
GreenTea1 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
Bode_20021 小时前
多资源规划(含生产调度、库存管理及产能规划)的创新优化算法
算法·调度
土司大王2 小时前
LeetCode hot100——35.搜索插入位置:Java 二分模板、左闭右开区间与插入点分析
java·算法·leetcode
微三云生态系统架构师-彭丹2 小时前
远方好物S2B2C系统架构:一级分销与保证金托管的合规技术实现
人工智能·算法
土司大王2 小时前
LeetCode hot100——34.在排序数组中查找元素的第一个和最后一个位置:Java 二分模板、边界分析
java·算法·leetcode
罗西的思考4 小时前
机器人模型(WM / WAM / VLA)综合分析与对比:从「看」到「想」再到「做」
人工智能·算法·机器学习
小哈里5 小时前
【知识】从学科到实践:自然・工程・社科・人文|算法・研发・产品・品牌设计
程序人生·算法·产品·设计·工程
小π军6 小时前
最大重叠区间数量
数据结构·算法
镜像视界(浙江)科技有限公司6 小时前
《视频孪生之上:二维展示终结,三维空间计算重构城市逻辑》——跨摄像连续表达 × 三角测量厘米级定位 × 动态轨迹建模,构建新一代城市空间
大数据·人工智能·算法·矩阵·音视频·空间计算
a187927218316 小时前
【算法】双指针与滑动窗口(二):滑动窗口——吃进、判定、吐出
算法·leetcode·双指针·滑动窗口·原理·模板·算法讲解