XCPC第十二讲,带你学会线段树
创始人
2025-06-01 20:34:53
0

在这里插入图片描述

        今天我们来学习线段树这一数据结构。线段树是一种二叉树,它的每一个节点存储一段区间的信息(如该区间内所有元素的和)。以终点为分界,每个节点衍生出两个子节点,直到某个节点表示的区间只有一个数。如图——
在这里插入图片描述

线段树(第一版)

在这里插入图片描述
        我们可以利用线段树来实现题目的需求。现在我们假设数列中有五个数,那么线段树如下——
在这里插入图片描述

第一种操作

        线段树中的每个端点存储的是这个端点所代表的区间内的元素的和。假若我们现在要给第三个数字加上y,那么我们可以发现,从第三个数字所在的最小区间(即只有它自己的区间)到根节点所表示的区间路径上的点表示的区间都要加y,这是因为它们都包含第三个数。

第二种操作

        要求得左端点为x右端点为y的区间内的元素和,我们的基本想法是把这个大区间分成一个个小区间,分别对每个小区间求和再加起来。在这个过程中,如果我们把某个节点的两个子节点都选上了,那么就可以直接选该节点而不选子节点,从而减少计算量。譬如我们要求【3,5】这个区间的元素和,我们把它分成【3,3】,【4,4】和【5,5】三个小区间。由于【4,5】的两个子区间都选了,我们就可以直接选【4,5】来代替它们两个。这样一来,由于区间是连续的,每层最多只可能选两个靠边的区间。如图——
在这里插入图片描述
        要求绿颜色范围内的区间,最后一层最多只会选两个区间。下面让我们来看看两种操作具体的代码实现吧!

#include
using namespace std;
//开数列元素的四倍大小来存储节点,这里不加证明
const int N = 2000010,M = 500010;
//f[i]表示第i个节点表示的区间(节点与区间时一一对应的)的元素和,root表示读入的原数列
int n,m,f[N],root[N];
inline void buildtree(int k,int l,int r)
{if(l==r){//如果l==r,说明递归到叶子节点(即一个区间只有一个值),那么此时的f[k](即下标为k的区间内元素的和)就是root[l]本身f[k] = root[l];//这里不要忘记return!return;}//取出区间的中点,递归地建立左子树和右子树int m = (l+r)>>1;buildtree(k+k,l,m);buildtree(k+k+1,m+1,r);//别忘了这一句话,要把左右两个子节点代表的区间的元素和加起来赋给父节点f[k] = f[k+k]+f[k+k+1];
}
//这句话意思是在下标k表示的左端点为l右端点为r的区间中的编号为x的数加上y
inline void add(int k,int l,int r,int x,int y)
{//编号为k的区间内的点x加上了y,那么该区间和自然也加上y,因为该区间含有xf[k]+=y;//如果l==r,说明已经递归到叶子节点,说明该路径上含有x的区间的和都加上了y,返回即可if(l==r)    return;int m = (l+r)>>1;//如果x落在左子树内,就递归地让左子树上含有x的区间和都加上y。x在右子树类似if(x<=m)    add(k+k,l,m,x,y);else add(k+k+1,m+1,r,x,y);
}
//这句话的意思是,在节点k代表的区间【l,r】内求区间【s,t】的元素和
inline int calculate(int k,int l,int r,int s,int t)
{//如果l==s&&r==t,那么要查询的区间就是当前区间,直接返回节点k代表的区间的元素和即可if(l==s&&r==t)    return f[k];int m = (l+r)>>1;//t<=m说明要查询的区间完全落在左半边if(t<=m)    return calculate(k+k,l,m,s,t);//s>m说明要查询的区间完全落在右半边else if(s>m)  return calculate(k+k+1,m+1,r,s,t);//否则要查询的区间横跨左右两边,只要分别查询并求和即可,相当于剖成两个区间了else    return calculate(k+k,l,m,s,m)+calculate(k+k+1,m+1,r,m+1,t);
}
int main()
{cin>>n>>m;for(int i = 1;i<=n;i++)cin>>root[i];buildtree(1,1,n);while(m--){int t,p,q;cin>>t>>p>>q;if(t==1)    add(1,1,n,p,q);else    cout<

线段树(标记版)

在这里插入图片描述

第一种操作

        与上一题不同的是,本题要求某区间内的每一个数都要加上y。为了达到这个目的,我们需要额外维护一个v数组。v[i] = k表示编号为i的节点对应的区间内的每个点要加上k。此时f数组的含义也会相应地发生变化,f[i] = k表示编号为i的节点对应的区间在不考虑v数组的影响下的和(注意,f数组只是排除了该区间之上区间的v的影响,并不意味着f数组存储的就是最初放入数列中的对应元素和,因为该区间下面也会有v数组的影响)。我们可以看到,v数组对应的实际上是“父树”(包括自身),因为“父树”总是包含当前区间;f数组对应的是“子树”(不包括自身),因为所有子树的并就是父树。如图——
在这里插入图片描述

第二种操作

        当我们要计算真正的某段区间内元素的和时,我们只需要合并考虑v数组和f数组的影响即可。由于该区间往上的区间都包含该区间,所以在考虑该区间的v数组影响时,我们要把前面的v值都累加起来,为此引进记录v值和的变量p。

#include
using namespace std;
const int N = 400010;
int n;
int m;
long long f[N];
long long v[N];
long long root[N];
void buildtree(int k,int l,int r)
{if(l==r){f[k] = root[l];return;}int m = (l+r)>>1;buildtree(k+k,l,m);buildtree(k+k+1,m+1,r);f[k] = f[k+k]+f[k+k+1];
}
//这句话意为在编号k代表的区间【l,r】内的【x,y】区间内的每个数都要加上z
void Insert(int k,int l,int r,int x,int y,long long z)
{if(l==x&&r==y){//此时要操作的区间就是【l,r】,那么根据v数组的含义,编号k代表的区间(就是【l,r】就要每个数加上z)v[k]+=z;return;}//由于【x,y】包含于【l,r】,且长度为(y-x+1)那么在不考虑v数组的影响下,节点k代表的区间就要加上(y-x+1)*zf[k] += (y-x+1)*z;int m = (l+r)>>1;//类上题,递归地插入if(y<=m)    Insert(k+k,l,m,x,y,z);else if(x>=m+1) Insert(k+k+1,m+1,r,x,y,z);else    Insert(k+k,l,m,x,m,z),Insert(k+k+1,m+1,r,m+1,y,z);
}
long long calculate(int k,int l,int r,int x,int y,long long p)
{//p记录的是当前节点往上的路径上的点的v的和,那么加上这一层的v后就变成包括这一层的往上的路径上的点的v的和p+=v[k];if(l==x&&r==y)//上下两部分的和加起来return p*(y-x+1)+f[k];int m = (l+r)>>1;//类似第,递归计算左子树的和与右子树的和if(y<=m)    return calculate(k+k,l,m,x,y,p);else if(x>=m+1) return calculate(k+k+1,m+1,r,x,y,p);else    return calculate(k+k,l,m,x,m,p)+calculate(k+k+1,m+1,r,m+1,y,p);
}
int main()
{scanf("%d%d",&n,&m);for(int i = 1;i<=n;i++) scanf("%d",&root[i]);buildtree(1,1,n);while(m--){int t;scanf("%d",&t);if(t==1){int x,y;//别忘了k要以long long形式读入!long long k;scanf("%d%d%lld",&x,&y,&k);Insert(1,1,n,x,y,k);}else{int x,y;scanf("%d%d",&x,&y);//注意输出格式!别忘了换行!printf("%lld\n",calculate(1,1,n,x,y,0));}}return 0;

线段树(下放标记版)

在这里插入图片描述
        在上一个版本的基础上我们能否再进行改进呢?如果我们不引入记录v值和的p变量,就要设法将前面的v值置为0。为此我们这样考虑:假若某父节点的v值增加了k,不就相当于它的两个子节点的v值都增加v么?这是因为,两个子节点的并恰好就是父节点呀!这样一来,我们就执行了一个标记“层层下放”的操作,直到当前要查询的区间,它的v值就是前面的累加了。但是这样的“下放”会带来一个问题,即f数组的数据变得混乱。为了修正f数组,我们可以考虑f数组是怎么生成的?它表示的是不考虑该区间以上区间v数组影响的情况下该区间的元素和。换句话说,f数组的值只与该区间以下的f值好v值有关。因此我们实际上可以将区间按照左右子树写出这样的关系式:f[k] = f[k+k]+v[k+k](m-l+1)+f[k+k+1]+v[k+k+1](r-m),这里m表示区间【l,r】的中点,即m = (l+r)>>1。

#include
using namespace std;
const int M = 400010,N = 100010;
//真服了,以后干脆什么都开long long得了
long long f[M],root[N],v[M],n,m;
void buildtree(int k,int l,int r)
{if(l==r){f[k] = root[l];return;}int m = (l+r)/2;buildtree(k+k,l,m);buildtree(k+k+1,m+1,r);f[k] = f[k+k]+f[k+k+1];
}
void Insert(int k,int l,int r,int x,int y,long long z)
{if(l==x&&r==y) {v[k]+=z;return;}//如果某点v值不为0,我们就“下放”,让它的两个子节点的v值增加等价于if(v[k]){v[k+k]+=v[k];v[k+k+1]+=v[k];v[k] = 0;}int m = (l+r)/2;if(y<=m)    Insert(k+k,l,m,x,y,z);else if(x>=m+1)     Insert(k+k+1,m+1,r,x,y,z);else    Insert(k+k,l,m,x,m,z),Insert(k+k+1,m+1,r,m+1,y,z);//根据f数组的定义修复f数组f[k] = f[k+k]+v[k+k]*(m-l+1)+f[k+k+1]+v[k+k+1]*(r-m);
}
long long calculate(int k,int l,int r,int x,int y)
{
//如果此条件满足,说明要查询的区间与编号k对应的区间相同,我们直接返回编号k对应区间的元素和的表达式即可if(l==x&&r==y)  return v[k]*(r-l+1)+f[k];//由于f数组还未修复,我们不能直接返回递归函数的值,而应该先将它用临时变量存储起来long long res = 0;//标记下放if(v[k]){v[k+k]+=v[k];v[k+k+1]+=v[k];v[k] = 0;}int m = (l+r)/2;if(y<=m)    res = calculate(k+k,l,m,x,y);else if(x>=m+1)     res = calculate(k+k+1,m+1,r,x,y);else    res = calculate(k+k,l,m,x,m)+calculate(k+k+1,m+1,r,m+1,y);f[k] = f[k+k]+v[k+k]*(m-l+1)+f[k+k+1]+v[k+k+1]*(r-m);return res;
}
int main()
{scanf("%d%d",&n,&m);for(int i = 1;i<=n;i++) scanf("%d",&root[i]);buildtree(1,1,n);while(m--){int t;scanf("%d",&t);if(t==1){int x,y;//别忘了k要以long long形式读入!long long k;scanf("%d%d%lld",&x,&y,&k);Insert(1,1,n,x,y,k);}else{int x,y;scanf("%d%d",&x,&y);//注意输出格式!别忘了换行!printf("%lld\n",calculate(1,1,n,x,y));}}return 0;
}

好啦,以上就是本讲的全部内容。若有任何关于文章的问题或批评,都欢迎在评论区指出,博主会进行回复!

相关内容

热门资讯

“新政策”彰显中国市场开放性 近日,在拖轮助力下,载着上万个集装箱的“中远海运杜鹃”轮缓缓驶进天津港。得益于天津港中远海运美东航线...
雅博光伏E周刊:“两办:202... 01E点聚焦 两办定目标:碳排放权、用水权交易制度2027年基本完善。 据新华社5月29日消息,中...
为孤独症人群提供全生命周期服务... 为孤独症人群提供全生命周期服务需体系化法律保障 □ 本报记者 文丽娟 周斌 陈磊 孤独症谱系障碍(...
青海省总就安全生产发出劳动法律... 原标题:青海省总就安全生产发出劳动法律监督提示函(引题) 严禁强令职工冒险作业(主题) 中工网讯 (...
普惠托育服务咋收费,我省发布最... 近日,山西晚报·山河+记者从省发改委获悉:我省发布完善普惠托育服务收费新政策,明确了涵盖公办托育机构...
生活垃圾管理条例实施细则印发 ... 近日,市政府办公室正式印发《昆明市生活垃圾管理条例实施细则》(以下简称《细则》),昆明将开展二手商品...
四维图新:正式发布《市值管理制... 金融界6月3日消息,有投资者在互动平台向四维图新提问:董秘,您好!公司股价近期出现底部放量上涨,是否...
云南省重点高原湖泊入湖河道保护... 云南省人民代表大会常务委员会公告 〔十四届〕 第四十四号 《云南省重点高原湖泊入湖河道保护条例》已由...
英国商界人士:美关税政策成跨国... 近日,多名英国商界人士表示,美国关税政策加剧全球经济不确定性,成为跨国企业运营中的“关键障碍”。英国...
苹果对欧盟要求开放生态系统的指... 苹果已对欧盟要求其向Meta、谷歌母公司Alphabet等竞争对手开放封闭生态系统的命令提出法律挑战...