1.简介
线段树是算法竞赛中常用的用来维护区间信息的数据结构。
线段树可以在很小的时间复杂度内实现单点修改、区间修改、区间查询(即区间求和,求区间 max,求区间min,区间gcd)等操作。
但是,线段树所维护的信息,需要满足区间加法。
区间加法:如果一个区间 [l,r](线段树中一个点表示一个区间)满足区间加法的意思是一个区间 [l,r] 的线段树维护的信息(即区间最大值,区间最小值,区间和,区间gcd 等),可以由两个区间 [l,mid] 和 [mid+1,r] 合并而来。
2.主要特点
1.线段树是一种特殊的平衡二叉查找树,使用线段树可以实现数据的添加、查找和删除等操作
2.线段树的每一个节点表示一个区间
3.线段树的叶子节点表示的区间为 [x,x],且长度为 1
3.线段树的建立
结构体
#include<iostream>
using namespace std;
const int N=1e5+10;
typedef lc p<<1;
typedef rc p<<1|1;
int n,w[N];
struct Node{
int l,r,sum;
}tree[4*N];
void build(int p,int l,int r)
{
tree[p]={l,r,w[l]};
if(l==r) return;
int m=l+r<<1;
build(lc,l,m);
build(rc,m+1,r);
tree[p].sum=tree[lc].sum+tree[rc].sum;
}
void buildtree(int p,int l,int r)
{
v[p]=0;
if(l==r)
{
f[p]=a[l];
return;
}
int mid=l+r>>1;
buildtree(p+p,l,mid);
buildtree(p+p+1,mid+1,r);
//分别递归左右子树
f[p]=f[p+p]+f[p+p+1];
//重新计算这个节点所覆盖的元素的和。
}
4.插入
void tree_insert(int p,int l,int r,int num)//num表示待插入数字
{
a[p]++;//[l,r]中的数字增加了一个
if(num==l&&num==r) return; //当前区间为单位区间,即叶子节点,函数返回
int mid=l+r>>1;
int lc=p*2+1;//数组从0开始
int rc=p*2+2;
if(num<=mid) tree_insert(lc,l,mid,num);
else tree_insert(rc,mid+1,r,num);
}
5.查找
int tree_search(vector<int>&a,int p,int l,int r,int num)
{
if(num==l&&num==r) return a[p];
int mid=l+r>>1;
int lc=p*2+1;
int rc=p*2+2;
if(num<=mid) return tree_search(a,lc,l,mid,num);
else return tree_search(a,rc,mid+1,r,num);
}
6.单点修改
void add(int p,int l,int r,int x, int y)
{
f[p]+=y;//修改当前点的区间和
if(l==r) return;
int mid=l+r>>1;
if(x<=m) add(k+k,l,mid,x,y);
else add(k+k+1,mid+1,r,x,y);
}
int add(int p,int l,int r,int x,int y)
{
if(l==x&&r==y) return f[p];
int mid=l+r>>1;
if(y<=mid) return calc(p+p,l,mid,x,y)
else
if(x>mid) return calc(p+p+1,mid+1,r,x,y);
else return calc(p+p,l,mid,x,mid)+calc(p+p+1,mid+1,r,mid+1,y);
}
7.区间修改
打标记
void insert(int p,int l,int r,int x,int y,LL k)//区间[x,y]内的每个元素加k
{
if(l==x&&r==y)
{
v[p]+=k;
return;
}
f[p]+=(y-x+1)*k;
int mid=l+r>>1;
if(y<=mid) insert(p+p,l,mid,x,y,k);//区间在左子树上
else
if(x>mid) insert(p+p+1,mid+1,r,x,y,k);//区间在右子树上
else//如果横跨两棵树
{
insert(p+p,l,mid,x,mid,k);//左子树
insert(p+p+1,mid+1,r,mid+1,y,k);//右子树
}
}
标记下传
void insert(int p,int l,int r,int x,int y,LL k)//区间[x,y]内的每个元素加k
{
if(l==x&&r==y)
{
v[p]+=k;
return;
}
if(v[p]) v[p+p]+=v[p],v[p+p+1]+=v[p],v[p]=0;
//清除p节点的标记v[p],即把左子树和右子树分别加上v[p]
int mid=l+r>>1;
if(y<=mid) insert(p+p,l,mid,x,y,k);//区间在左子树上
else
if(x>mid) insert(p+p+1,mid+1,r,x,y,k);//区间在右子树上
else//如果横跨两棵树
{
insert(p+p,l,mid,x,mid,k);//左子树
insert(p+p+1,mid+1,r,mid+1,y,k);//右子树
}
f[p]=f[p+p]+v[p+p]*(mid-l+1)+f[p+p+1]+v[p+p+1]*(r-(mid+1)+1);
}
8.区间求和
打标记
LL calc(int p,int l,int r,int x,int y,LL z)//z表示vi之和
{
z+=v[p];
if(l==x&&r==y) return z*(r-l+1)+f[p];
int mid=l+r>>1;
if(y<=mid) return calc(p+p,l,mid,x,y,z);
else
if(x>mid) return calc(p+p+1,mid+1,r,x,y,z);
else return calc(p+p,l,mid,x,mid,z)+calc(p+p+1,mid+1,r,mid+1,y,z);
}
标记下传
LL calc(int p,int l,int r,int x,int y)
{
if(l==x&&r==y) return f[p]+v[p]*(r-l+1);
if(v[p])
v[p+p]+=v[p],v[p+p+1]+=v[p],v[p]=0;//标记下传
int mid=l+r>>1;
LL res=0;//不能直接return,f[]还没有更新,需要收回f[]
if(y<=mid) res=calc(p+p,l,mid,x,y);
else
if(x>mid) res=calc(p+p+1,mid+1,r,x,y);
else res=calc(p+p,l,mid,x,mid)+calc(p+p+1,mid+1,r,mid+1,y);
f[p]=f[p+p]+v[p+p]*(mid-l+1)+f[p+p+1]*+v[p+p+1]*(r-mid);
return res;
}
代码:
#include<iostream>
using namespace std;
const int N=1e5+10;
typedef long long LL;
LL f[4*N],a[N],v[4*N];
//f[]的唯一目的,就是记录当前节点所代表的区间
//v[]表示当前这一个点对应的区间中的每个数需要加的值
int n,m;
void buildtree(int p,int l,int r)
{
v[p]=0;
if(l==r)
{
f[p]=a[l];
return;
}
int mid=l+r>>1;
buildtree(p+p,l,mid);
buildtree(p+p+1,mid+1,r);
//分别递归左右子树
f[p]=f[p+p]+f[p+p+1];
//重新计算这个节点所覆盖的元素的和。
}
void insert(int p,int l,int r,int x,int y,LL k)//k表示待加的数
{
if(l==x&&r==y)
{
v[p]+=k;
return;
}
f[p]+=(y-x+1)*k;
int mid=l+r>>1;
if(y<=mid) insert(p+p,l,mid,x,y,k);//区间在左子树上
else
if(x>mid) insert(p+p+1,mid+1,r,x,y,k);//区间在右子树上
else//如果横跨两棵树
{
insert(p+p,l,mid,x,mid,k);//左子树
insert(p+p+1,mid+1,r,mid+1,y,k);//右子树
}
}
LL calc(int p,int l,int r,int x,int y,LL z)//z表示vi之和
{
z+=v[p];
if(l==x&&r==y) return z*(r-l+1)+f[p];
int mid=l+r>>1;
if(y<=mid) return calc(p+p,l,mid,x,y,z);
else
if(x>mid) return calc(p+p+1,mid+1,r,x,y,z);
else return calc(p+p,l,mid,x,mid,z)+calc(p+p+1,mid+1,r,mid+1,y,z);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
buildtree(1,1,n);//根节点为1,区间为[1,n]建树
while(m--)
{
int t;
cin>>t;
if(t==1)
{
int x,y;
LL k;
scanf("%d%d%lld",&x,&y,&k);
insert(1,1,n,x,y,k);
}
if(t==2)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",calc(1,1,n,x,y,0));
}
}
return 0;
}