Day5(线段树)

线段树:

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值