news 2026/7/19 4:31:31

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HDU4348 To the moon题解(主席树区间修改模板)

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

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

题解
简化题意
给定一个长度为
的序列
以及
个操作,操作有如下四种:




:给
内的每个数加上
,并将时间




:查询当前
内所有数的和。



:查询
时,
内所有数的和。

:返回时间

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

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

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

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

怎么办呢?

方法一
容易想到的一种方案是:

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

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

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

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

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

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

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

#include
#include
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(lr){
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;
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 4:29:41

OpenGauss MOT表实现代码走读

一. 前言MOT表是openGauss中一个重要的亮点特性&#xff0c;其得益于本地内存和全局内存的设计实现了无锁操作&#xff0c;Mass索引和commit的时候才统一做checkpoint等能力实现了高并发读写。本文主要走读openGauss中的代码&#xff0c;了解其如何实现的。二. MOT表读流程代码…

作者头像 李华
网站建设 2026/7/19 4:26:43

C高级Linux远程终端管理系统

前言在职坐标学习的过程中&#xff0c;每学一门编程后面都对应相应的开发项目。软件开发环节中需求分析尤为重要&#xff0c;它关系着程序各个模块功能的实现、项目进度以及最终产品的质量。作为初学者在第一次面对如此集成化的开发任务时或多或少会一脸茫然&#xff0c;不知道…

作者头像 李华
网站建设 2026/7/19 4:26:16

Python项目打包指南:setup.py配置与最佳实践

1. Python项目打包概述&#xff1a;为什么需要setup.py&#xff1f;在Python开发中&#xff0c;打包分发是项目从开发环境走向生产环境的关键步骤。不同于直接将源码文件夹复制到目标机器的原始方式&#xff0c;规范的打包操作能解决依赖管理、版本控制和跨平台兼容性等核心问题…

作者头像 李华
网站建设 2026/7/19 4:25:31

Android开源项目选型与实现原理深度解析

1. Android开源项目生态概述作为一名有8年Android开发经验的工程师&#xff0c;我深刻体会到开源项目对开发效率的提升作用。Android开源生态经过十余年发展&#xff0c;已经形成了覆盖UI组件、网络通信、图片处理、架构设计等全领域的成熟体系。根据GitHub官方数据&#xff0c…

作者头像 李华
网站建设 2026/7/19 4:24:21

Unity弧形文本UI实战:5分钟打造动态圆形菜单

1. 项目概述&#xff1a;为什么弧形文本是UI设计的点睛之笔在游戏和交互应用的UI设计中&#xff0c;菜单和按钮的排列方式直接影响了用户的视觉体验和操作直觉。传统的线性排列虽然清晰&#xff0c;但在追求沉浸感和视觉冲击力的项目中&#xff0c;往往显得过于呆板。这时&…

作者头像 李华