线段树
一种用于区间处理的工具。当我们遇到求区间的最值,区间的和,区间修改数据的时候,如果有数组进行操作,复杂度有的时候可达到O(m*n),当数据量比较大的时候,就不可以采取这种方法了。
线段树是用一种大概类似于二叉树的方式所建立。二叉树在查找一个数据的时候,是二叉树折半查找的方式进行的,复杂度是O(log2^n),效率比较高。
最值问题
每个结点上的数字是这颗子树上的最小值。
建立
每个结点的值为他的左孩子和右孩子的和。当左右两边相等的时候,就是当前数组的值,然后结束递归,进行回溯。
void build_tree(int arr[],int tree[],int node,int start,int endd)//数组,树,当前结点,左端点,右端点
{
if(start==endd){//如果左右端点相等,即树的值为数组目前的值
tree[node]=arr[start];
}
else{
int mid=(start+endd)/2;//中间值
int left_node =2*node+1;//左孩子的结点值
int right_node=2*node+2;//右孩子的结点值
build_tree(arr,tree,left_node,start,mid);//左子树,左右端点折半
build_tree(arr,tree,right_node,mid+1,endd);//右子树,左右端点折半
tree[node]=tree[left_node]+tree[right_node];//结点的值为左右孩子的值相加
}
}
点修改
修改一个数组中特定的点,要先搜索找到它的位置,然后修改,回溯的时候,将它所涉及的区间结点的值进行修改。
void update_tree(int arr[],int tree[],int node,int start,int endd,int idx,int val)//数组,树,当前结点,左端点,右端点,需要修改的下标,修改后的值
{
if(start==endd){//如果左右端点相等,修改树当前结点的值,修改数组下标的值
tree[node]=val;arr[idx]=val;
}
else{
int mid=(start+endd)/2;
int left_node=2*node+1;
int right_node=2*node+2;
if(idx>=start&&idx<=mid){//如果需要修改的下标在左孩子的区间中
update_tree(arr,tree,left_node,start,mid,idx,val);
}
else{//如果需要修改的下标在右孩子的区间中
update_tree(arr,tree,right_node,mid+1,endd,idx,val);
}
tree[node]=tree[left_node]+tree[right_node];//回溯时将它涉及的树结点修改,结点=左孩子的值+右孩子
//cout<<node<<tree[node]<<endl;
}
}
区间和
求一个区间的和的时候,要看他的区间的左右区间在哪边,然后进行查找,并返回值,最后将左右的值相加返回。
int query_tree(int arr[],int tree[],int node,int start,int endd,int L,int R)//数组,树,当前结点,左端点,右端点,区间的左端点,区间的右端点
{
if(endd<L||start>R){//如果目前结点不在所要求和的区间内
return 0;//就返回0
}
else if(start==endd){//如果左右端点相等
return tree[node];//就返回当前结点的值,即已经找到了那个对应的叶子结点
}
else if(start>=L&&endd<=R){//如果目前左右端点整个都在求和的区间内
return tree[node];//那就可以直接返回这个结点的值了,即左右端点的和,就不用向下继续查找了
}
else{
int mid=(start+endd)/2;
int node_left=2*node+1;
int node_right=2*node+2;
int sum_left=query_tree(arr,tree,node_left,start,mid,L,R);//左孩子的值
int sum_right=query_tree(arr,tree,node_right,mid+1,endd,L,R);//右孩子的值
//cout<<sum_left<<" "<<sum_right<<endl;
return sum_left+sum_right;//回溯时将值一步一步的向上回溯,最后将总值返回根节点
}
}
完整的代码
#include<bits/stdc++.h>
using namespace std;
int arr[]={1,3,5,7,9,11};
int len=6;
int tree[110]={0};
void build_tree(int arr[],int tree[],int node,int start,int endd)
{
if(start==endd){
tree[node]=arr[start];
}
else{
int mid=(start+endd)/2;
int left_node =2*node+1;
int right_node=2*node+2;
build_tree(arr,tree,left_node,start,mid);
build_tree(arr,tree,right_node,mid+1,endd);
tree[node]=tree[left_node]+tree[right_node];
}
}
void update_tree(int arr[],int tree[],int node,int start,int endd,int idx,int val)
{
if(start==endd){
tree[node]=val;arr[idx]=val;
}
else{
int mid=(start+endd)/2;
int left_node=2*node+1;
int right_node=2*node+2;
if(idx>=start&&idx<=mid){
update_tree(arr,tree,left_node,start,mid,idx,val);
}
else{
update_tree(arr,tree,right_node,mid+1,endd,idx,val);
}
tree[node]=tree[left_node]+tree[right_node];
//cout<<node<<tree[node]<<endl;
}
}
int query_tree(int arr[],int tree[],int node,int start,int endd,int L,int R)
{
if(start==endd){
return tree[node];
}
else if(endd<L||start>R){
return 0;
}
else if(start>=L&&endd<=R){
return tree[node];
}
else{
int mid=(start+endd)/2;
int node_left=2*node+1;
int node_right=2*node+2;
int sum_left=query_tree(arr,tree,node_left,start,mid,L,R);
int sum_right=query_tree(arr,tree,node_right,mid+1,endd,L,R);
cout<<sum_left<<" "<<sum_right<<endl;
return sum_left+sum_right;
}
}
int main ()
{
ios::sync_with_stdio(false);
build_tree(arr,tree,0,0,len-1);
//for(int i=0;i<15;i++){
// cout<<tree[i]<<endl;
//}
//cout<<endl;
update_tree(arr,tree,0,0,len-1,4,10);
for(int i=0;i<15;i++){
cout<<tree[i]<<endl;
}
cout<<endl;
int ans=query_tree(arr,tree,0,0,len-1,2,5);
cout<<ans<<endl;
return 0;
}
这种比较麻烦,而且对于修改区间不太方便
另一种办法
进行“懒惰”标记,进行标记,等用到了这个区间,就按照这个标记进行修改
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e5+9;
typedef long long ll;
ll tree[3*N];
ll dis[3*N];
ll mp[N];
void build(ll l,ll r,ll node)//建树
{
dis[node]=0;
if(l==r){
tree[node]=mp[l];return;
}
ll mid=(l+r)/2;
build(l,mid,2*node);
build(mid+1,r,2*node+1);
tree[node]=tree[2*node]+tree[2*node+1];
}
void down(ll node,ll s)//这个标记被破坏了,就就将标记向下传递,自己本身的标记消失
{
if(dis[node]){
dis[node<<1]+=dis[node];//左孩子标记
dis[node<<1|1]+=dis[node];//右孩子标记
tree[node<<1]+=(s-(s>>1))*dis[node];//左结点值进行改变
tree[node<<1|1]+=(s>>1)*dis[node];//右结点进行改变
dis[node]=0;//标记消失
}
}
void xiu(ll a,ll b,ll c,ll l,ll r,ll node)//区间修改
{
if(l>=a&&r<=b){//在区间内,就整个区间进行修改
tree[node]+=(r-l+1)*c;
dis[node]+=c;
return;
}
down(node,r-l+1);//被破坏了,更新标记
ll mid=(l+r)/2;//分成两部分
if(a<=mid)//小于mid的部分,就是左边子树部分
xiu(a,b,c,l,mid,2*node)
if(b>mid)
xiu(a,b,c,mid+1,r,2*node+1);;//大于mid的部分,就是右边子树部分
tree[node]=tree[2*node]+tree[2*node+1];//之后左右子树相加,进行tree数组的修改
}
ll qiu(ll a,ll b,ll l,ll r,ll node)//求和,与之前的操作相同
{
if(l>=a&&r<=b) return tree[node];
down(node,r-l+1);
ll sum=0;
ll mid=(l+r)/2;
if(a<=mid)
sum+=qiu(a,b,l,mid,2*node);
if(b>mid)
sum+=qiu(a,b,mid+1,r,2*node+1);
return sum;
}
int main ()
{
ll n,m;
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&mp[i]);
}
build(1,n,1);
while(m--)
{
getchar();
char ch;scanf("%c",&ch);
if(ch=='C'){
ll a,b,c;scanf("%lld %lld %lld",&a,&b,&c);
xiu(a,b,c,1,n,1);
}
else{
ll a,b;scanf("%lld %lld",&a,&b);
ll ans=qiu(a,b,1,n,1);
printf("%lld\n",ans);
}
}
return 0;
}