操作格子——线段树

问题描述
有n个格子,从左到右放成一排,编号为1-n。
共有m次操作,有3种操作类型:
1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。
对于每个2、3操作输出你所求出的结果。
输入格式
第一行2个整数n,m。
接下来一行n个整数表示n个格子的初始权值。
接下来m行,每行3个整数p,x,y,p表示操作类型,p=1时表示修改格子x的权值为y,p=2时表示求区间[x,y]内格子权值和,p=3时表示求区间[x,y]内格子最大的权值。
输出格式
有若干行,行数等于p=2或3的操作总数。
每行1个整数,对应了每个p=2或3操作的结果。
样例输入
4 3
1 2 3 4
2 1 3
1 4 3
3 1 4
样例输出
6
3
数据规模与约定
对于20%的数据n <= 100,m <= 200。
对于50%的数据n <= 5000,m <= 5000。
对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。




本题直接用常规的方法肯定会超时,所以只能用线段树的方法

线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。
线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
本题需要构造的线段树储存两个信息,一是区间内的和,二是区间内的最大值,当然为了写代码简便我们还需要在节点上储存上储存他们所代表的区间。
预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树
叶子节点是原始组数中的元素

非叶子节点储存两个信息一是它的所有子孙叶子节点所在区间的最大值,二是所有子孙节点所在区间内的和。


例如对于数组[2, 5, 1, 4, 9, 3](序号从一开始)可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值从前到后分别对应是其对应数组区间内的最大值和和,例如根节点表示数组区间[1-6]内的最大值是9,和是24)



      明白了结构对于线段树的插入,更新·,操作就和普通树差不多。我们可以定义一个结构体来描述树的每个节点,然后用结构体数组描述整棵树的情况,从1开始为根节点,若节点在数组中编号为i,则它的左孩子节点的编号为2i,右孩子节点标号为2i+1.


#include<iostream> 
#include<vector> 
#include<algorithm>
using namespace std;

int SUM,MAX;//用来记录所求区间内的和与最大值
struct SegTree
{
	int left,right,Max,sum;//分别节点所代表的左右区间,最大值,和
}seq[1000001];

void init(int l,int r,int i)//初始化l表示左区间,r表右区间,i表示自己的原序列编号
{
	seq[i].left=l;
	seq[i].right=r;
	seq[i].Max=0;
	seq[i].sum=0;
	if(l!=r)//如果没有到达叶子节点
	{
		int mid=(l+r)/2;
		init(l,mid,2*i);//递归初始化左子树
		init(mid+1,r,2*i+1);//递归初始化右子树
	}
}

void insert(int i,int x,int m)//插入操作,i表示根节点编号,x表示要插入的点在原序列的编号,m表示要插入点的值
{
	if(x>=seq[i].left&&x<=seq[i].right)//找到位置插入
	{
		seq[i].Max=m;
		seq[i].sum=m;
	}
	if(seq[i].left==seq[i].right)//找到叶节点返回
		return;
	int mid=(seq[i].left+seq[i].right)/2;
	if(x>mid)//如果序号在根的左区间,就把它往左子树上 
		insert(2*i+1,x,m);
	else//如果序号在根的右区间,就把它往右子树上插入
		insert(2*i,x,m);
	seq[i].sum=seq[2*i].sum+seq[2*i+1].sum;//每一次插入后更新一下根节点的sum与max值
	seq[i].Max=max(seq[2*i].Max,seq[2*i+1].Max);
}

int find_max(int x,int y,int i))//找最大值x,y表左右区间,i表节点编号
{
	if(x==seq[i].left&&y==seq[i].right)//如果找到就返回最大值return 
		return seq[i].Max;
	int mid=(seq[i].left+seq[i].right)/2;
	if(x>mid)//如果x,y区间序号在i节点的区间的右侧,就再从右区间里找
		return find_max(x,y,2*i+1);
	else if(y<=mid)
		return find_max(x,y,2*i);
	else//如果x,y在i节点的区间内,那么就得从i节点的左右孩子节点内找一个最大值
		return max(find_max(x,mid,2*i),find_max(mid+1,y,2*i+1));
}

int find_sum(int x,int y,int i)//与上面基本相同
{
	if(x==seq[i].left&&y==seq[i].right)
		return seq[i].sum;
	int mid=(seq[i].left+seq[i].right)/2;
	if(x>mid)
		return find_sum(x,y,2*i+1);
	else if(y<=mid)
		return find_sum(x,y,2*i);
	else
		return find_sum(x,mid,2*i)+find_sum(mid+1,y,2*i+1);
}
int main()  
{  
	
	int n,m,weight,p,x,y;
	cin>>n>>m;
	init(1,n,1);
	for(int i=1;i<=n;i++)
	{
		cin>>weight;
		insert(1,i,weight);
	}
	for(int i=0;i<m;i++)
	{
		cin>>p>>x>>y;
		if(p==1)
			insert(1,x,y);
		if(p==2)
			cout<<find_sum(x,y,1)<<endl;
		if(p==3)
			cout<<find_max(x,y,1)<<endl;	
	}	
	return 0;
}                                                 


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值