线段树
解决问题:
在数组区间中
更新某个数字(updata);
求数组某区间和(query);
解决方法一and 二
一. 直接用数组
方法:略
分析:
- updata复杂度是O(1)
-但 query复杂度O(n)
二. 前缀和
方法:
sum_a[i]=a[1]+...+a[i]
求解区间和时:a[j]+...a[i]=sum_a[i]-sum_a[j]
分析
- query复杂度O(1)
- 但updata复杂度O(n)
理解
每个叶子结点的值就是数组的值,
每个非叶子结点代表某区间和,
且左右两个孩子分别存储父亲一半的区间。
每个父亲的存储的值也就是两个孩子存储的值的和。
求和过程query
更改数据过程updata
建树
(不懂去看堆排序那章)
补成完全二叉树,用数组表示。
因此有性质:
(left代表左儿子,right代表右儿子)
代码
#include <bits/stdc++.h>
using namespace std;
#define MAX_LEN 1000
//node树的根节点
void build_tree(int arr[],int tree[],int node,int start,int end)
{
if(start == end)
{
tree[node]=arr[start];
return;
}
int mid = (start+end)/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,end);
tree[node]=tree[left_node]+tree[right_node];
}
int main()
{
int arr[] = {1,3,5,7,9,11};
int size = 6;
int tree[MAX_LEN] = {0};
build_tree(arr,tree,0,0,size-1);
int i;
for(i=0;i<=14;i++)
{
cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
}
return 0;
}
updata(更新数据)
void updata_tree(int arr[],int tree[],int node,int start,int end,int idx,int val)
{
if(start == end)
{
arr[idx]=val;
tree[node] = val;
return;
}
int mid = (start + end) / 2;
int left_node = 2*node +1;
int right_node =2*node + 2;
if(idx>start && idx<=mid)
{
updata_tree(arr,tree,left_node,start,mid,idx,val);
}
else
{
updata_tree(arr,tree,right_node,mid+1,end,idx,val);
}
tree[node]=tree[left_node]+tree[right_node];
}
query_tree 查询
int query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
if(R<start||L>end)//不在计算范围之内
{
return 0;
}
else
if(start ==end)
{
return tree[node];
}
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node +2;
int sum_left =query_tree(arr,tree,left_node,start,mid,L,R);
int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
问题出现在:递归出口是 单个结点;实际上只需要startend在RL之间即可返回
优化:
int query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
if(R<start||L>end)//不在计算范围之内
{
return 0;
}
else
if(start>=L&&end<=R)
{
return tree[node];
} else
if(start ==end)
{
return tree[node];
}
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node +2;
int sum_left =query_tree(arr,tree,left_node,start,mid,L,R);
int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
整体代码
#include <bits/stdc++.h>
using namespace std;
#define MAX_LEN 1000
//node树的根节点
void build_tree(int arr[],int tree[],int node,int start,int end)
{
if(start == end)
{
tree[node]=arr[start];
return;
}
int mid = (start+end)/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,end);
tree[node]=tree[left_node]+tree[right_node];
}
void updata_tree(int arr[],int tree[],int node,int start,int end,int idx,int val)
{
if(start == end)
{
arr[idx]=val;
tree[node] = val;
return;
}
int mid = (start + end) / 2;
int left_node = 2*node +1;
int right_node =2*node + 2;
if(idx>start && idx<=mid)
{
updata_tree(arr,tree,left_node,start,mid,idx,val);
}
else
{
updata_tree(arr,tree,right_node,mid+1,end,idx,val);
}
tree[node]=tree[left_node]+tree[right_node];
}
int query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
if(R<start||L>end)//不在计算范围之内
{
return 0;
}
else
if(start>=L&&end<=R)
{
return tree[node];
} else
if(start ==end)
{
return tree[node];
}
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node +2;
int sum_left =query_tree(arr,tree,left_node,start,mid,L,R);
int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
int main()
{
int arr[] = {1,3,5,7,9,11};
int size = 6;
int tree[MAX_LEN] = {0};
build_tree(arr,tree,0,0,size-1);
int i;
for(i=0;i<=14;i++)
{
cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
}
updata_tree(arr,tree,0,0,size-1,4,6);
cout<<endl;
cout<<"up_data:"<<endl;
for(i=0;i<=14;i++)
{
cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
}
int s = query_tree(arr,tree,0,0,size-1,2,5);
cout<<endl<<"query_tree: "<<s<<endl;
return 0;
}
实战
模板题
C - 数组计算机
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100007
long long int a[maxn];
long long int tree[4*maxn];
void build_tree(ll a[],ll tree[],int node,int start,int end)
{
if(start==end)
{
tree[node]=a[start];
return;
}
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node+2;
build_tree(a,tree,left_node,start,mid);
build_tree(a,tree,right_node,mid+1,end);
tree[node]=tree[left_node]+tree[right_node];
}
void updata(ll a[],ll tree[],int node,int start,int end,int p,int v)
{
if(start==end)//此时头==尾==p+1但是不等于p
{
//cout<<start<<"**9"<<endl;
tree[node]+=v;
a[p]+=v;
return;
}
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node+2;
if(p>start && p<=mid)
{
updata(a,tree,left_node,start,mid,p,v);
}
else //if(p>mid&&p<end)
{
updata(a,tree,right_node,mid+1,end,p,v);
}
tree[node]=tree[left_node]+tree[right_node];
}
ll query(ll a[],ll tree[],int node,int start,int end,int L,int R)
{
if(R<start||L>end)//不在计算范围之内
{
return 0;
}
else if(start>=L&&end<=R)
{
return tree[node];
}
else if(start ==end)
{
return tree[node];
}
int mid=(start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node+2;
ll sum_left= query(a,tree,left_node,start,mid,L,R);
ll sum_right= query(a,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset(tree,0,sizeof(tree));//清空树
int i;
for(i=1; i<=n; i++)
{
scanf("%lld",&a[i]);//数组
}
build_tree(a,tree,0,1,n);
int q;
scanf("%d",&q);
long long int p,v;
int L,R;
while(q--)
{
int key;
scanf("%d",&key);
if(key==1)
{
scanf("%lld %lld",&p,&v);
updata(a,tree,0,1,n,p,v);
}
else if(key==2)
{
scanf("%d%d",&L,&R);
printf("%lld\n",query(a,tree,0,1,n,L,R));
}
}
}
return 0;
}
D - 效率至上
思路:
套用线段树模板,在线段树里加两个值,分别记录当前区间内的最小值和最大值,然后只需要每次选出区间内的最大值和最小值,将他们做减法输出即可。