线段树:
1.线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
2.使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
3.使用时一般开4N的数组以免越界。
4.区间修改不能将L到R区间都进行一次单点修改,这样每次修改的复杂度为O(nlogn),是会超时的,需要使用(差分+单点修改)或者(线段树延迟标记)。
线段树的建立:
void build_tree(int arr[],int tree[],int node,int start,int end)
{
if(start==end) tree[node]=arr[start];
else
{
int mid=start+end>>1;
int left_node=2*node;
int right_node=2*node+1;
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 update_tree(int arr[],int tree[],int node,int start,int end,int idx,int val)
{
if(start==end)
{
arr[idx]=val;
tree[node]=val;
}
else
{
int mid=start+end>>1;
int left_node=2*node;
int right_node=2*node+1;
if(idx>=start&&idx<=mid) update_tree(arr,tree,left_node,start,mid,idx,val);
else update_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(l<=start&&r>=end) return tree[node];
else if(start==end) return tree[node];
else
{
int mid=start+end>>1;
int left_node=2*node;
int right_node=2*node+1;
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;
}
}
维护区间信息例题:
将您获得一个序列 A{1}、A{2}、…、A{N}。( |A[i]|≤ 15007 , 1 ≤ N ≤ 500000 )。查询的定义如下:
查询(x,y) = Max = a{i}a{1}…[a]j=≤ i ≤ j ≤ y]。
给定 M 查询,程序必须输出这些查询的结果。
输入
输入文件的第一行包含整数 N。
在第二行中,N 个数字跟随。
第三行包含整数 M。
M 行跟随, 其中行 i 包含 2 个数字 xi 和 yi 。
输出
程序应输出 M 查询的结果,每行一个查询。
示例输入
3
-1 2 3
1
1 2
示例输出
2
ac代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
struct segmentTree {
int l, r, sum, lmx, rmx, ans;
//l、r表示该节点维护的区间左右端点
//sum表示该区间和
//lmx表示该区间从最左边点开始向右扩展的最大值
//rmx表示该区间从最右边点开始向左扩展的最大值
//ans表示该区间的最大子段和
} node[N << 2];
int a[N];
void pushUp(int i) {
//对左右儿子使用区间结合律更新父亲的信息
node[i].sum = node[i<<1].sum + node[i<<1|1].sum;
node[i].lmx = max(node[i<<1].lmx, node[i<<1].sum + node[i<<1|1].lmx);
node[i].rmx = max(node[i<<1|1].rmx, node[i<<1|1].sum + node[i<<1].rmx);
node[i].ans = max({node[i<<1].ans, node[i<<1|1].ans, node[i<<1].rmx + node[i<<1|1].lmx});
}
void build(int l, int r, int i) {
node[i] = {l, r};
if (l == r) {
//初始化叶子节点,只有一个点的时候sum、lmx、rmx、ans都为a[i]
node[i] = {l, r, a[l], a[l], a[l], a[l]};
return;
}
int mid = l + r >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
pushUp(i);
}
segmentTree merge(segmentTree L, segmentTree R) {
//合并左右两个线段树并返回合并后的线段树,与pushUp的过程完全一致
segmentTree res;
res.sum = L.sum + R.sum;
res.lmx = max(L.lmx, L.sum + R.lmx);
res.rmx = max(R.rmx, R.sum +L.rmx);
res.ans = max({L.ans, R.ans, L.rmx + R.lmx});
return res;
}
segmentTree query(int l, int r, int i) {//返回将l到r区间合并后的线段树
if (l <= node[i].l && r >= node[i].r) return node[i];
//如果查询节点完全包含于查询区间直接返回该节点
int mid = node[i].l + node[i].r >> 1;
if (r <= mid) return query(l, r, i << 1); //如果查询区间完全包含于左子树则往左边查
else if (l > mid) return query(l, r, i << 1 | 1);//如果查询区间完全包含于右子树则往左边查
else {
//否则说明查询的区间横跨左右子树
segmentTree L = query(l, r, i << 1);
//将查询左边的信息记录在L线段树中
segmentTree R = query(l, r, i << 1 | 1);
//将查询右边的信息记录在R线段树中
return merge(L, R);
//返回L和R合并后的线段树
}
}
int main() {
int n, m;
scanf("%d",&n);
for (int i = 1; i <= n; i++)
scanf("%d",&a[i]);
build(1, n, 1);
scanf("%d",&m);
while (m--) {
int l, r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l, r, 1).ans);
}
return 0;
}