问题描述
有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。
问题分析:
看到题目想到用数组。再细看,对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。如果用数组的话,对于一次操作1,时间复杂度为O(1)。但是一次操作2或者操作3的时间复杂度为O(n),操作次数是m.整个问题的时间复杂度为O(m*n).达到了10^10次方,而计算机平均1s的运算速率是10^8次方。用数组,无论怎么优化都会超时。
后来想到了用线段树。为了便于描述,假设n=5,格子的初始值为1,3,5,2,10.
首先建立线段树(线段树的节点left和right是数组的下标值),初始化让所有节点的sum和max都为0。
然后用输入的初识数据来更新线段树的max和sum值。首先是1,从根节点开始[1,5]的max=1,sum=1.[1,3]的max=1,sum=1.[1,2]的max=1,sum=1,[1,1]的max=1,sum=1.然后是3,[1,5]的max=3,sum=1+3.[1,3]的max=3,sum=1+3.[1,2]的max=3,sum=1+3.[2,2]的max=3,sum=3.然后是5,[1,5]的max=5,sum=1+3+5.[1,3]的max=5,sum=1+3+5.[3,3]的max=5,sum=5..然后是2,[1,5]的max=5,sum=1+3+5+2.[4,5]的max=2,sum=2.[4,4]的max=2,sum=2..然后是10,[1,5]的max=10,sum=1+3+5+2+10.[4,5]的max=10,sum=2+10.[5,5]的max=10,sum=10.至此,操作2和操作3便可以通过查找找到对应的区间的sum和max.如果没有直接对应的区间,比如[1,4].sum([1,4])=sum([1,3])+sum([4,4]).max([1,4])=max([1,3])>max([4,4])?max([1,3]):max([4,4]).
操作1需要更改下标为x的数据为y.使用递归,首先向下递归找到[x,x]的节点。更改它的sum和max值都为y,然后向上回溯修改上面的节点。上面节点的sum为左孩子的sum加上右孩子的sum。上面节点的最大值为左孩子max和右孩子max中的较大值。
#include<cstdio>
#include<cstdlib>
using namespace std;
#define INF 32767
typedef struct LiTNode{
int left;
int right;
int max;
int sum;
struct LiTNode *lchild,*rchild;
}*LiTree;
void createLineTree(LiTree &T,int left,int right);
void InsertPoint(LiTree &T,int x,int value[]);
void ModifyLiTree(LiTree &T,int x,int y);
int getSum(LiTree &T,int x,int y);
int getMax(LiTree &T,int x,int y);
int main()
{
int m,n,i,max=-INF,min=INF;
int input[3];
LiTree T;
scanf("%d%d",&m,&n);
int *value=(int *)malloc(sizeof(int)*(m+1));
for(i=1;i<=m;i++)
{
scanf("%d",&value[i]);
}
createLineTree(T,1,m);
for(i=1;i<=m;i++)
{
InsertPoint(T,i,value);
}
for(i=0;i<n;i++)
{
scanf("%d%d%d",&input[0],&input[1],&input[2]);
if(input[0]==1)
ModifyLiTree(T,input[1],input[2]);
else if(input[0]==2)
printf("%d\n",getSum(T,input[1],input[2]));
else if(input[0]==3)
printf("%d\n",getMax(T,input[1],input[2]));
}
free(value);
return 0;
}
void createLineTree(LiTree &T,int left,int right)
//T指向所要创建的线段树,left和right线段树的左右区间,也就是初始值的最小和最大下标
{
T=(LiTree)malloc(sizeof(LiTNode));
T->left=left;
T->right=right;
T->max=T->sum=0;
T->lchild=T->rchild=NULL;
if(T->left==T->right)
return ;
int mid=(left+right)/2;
createLineTree(T->lchild,left,mid);
createLineTree(T->rchild,mid+1,right);
}
void InsertPoint(LiTree &T,int x,int value[])
//将输入的初始值插入线段树,并且跟新每个节点区间的最大值与和。
{
LiTree temp=T;
while(temp)
{
if(value[x]>temp->max)
temp->max=value[x];
temp->sum+=value[x];
int mid=(temp->left+temp->right)/2;
if(x<=mid)
temp=temp->lchild;
else
temp=temp->rchild;
}
}
//将下标为x的元素值修改为y
void ModifyLiTree(LiTree &T,int x,int y)
{
int left=T->left;
int right=T->right;
int mid=(left+right)/2;
if(left==right)
{
T->max=y;
T->sum=y;
return ;
}
if(x<=mid)
ModifyLiTree(T->lchild,x,y);
else
ModifyLiTree(T->rchild,x,y);
T->max=T->lchild->max>T->rchild->max?T->lchild->max:T->rchild->max;
T->sum=T->lchild->sum+T->rchild->sum;
}
//获得区间[x,y]的权值和
int getSum(LiTree &T,int x,int y)
{
int mid=(T->left+T->right)/2;
if(T->left==x&&T->right==y)
{
return T->sum;
}
else if(y<=mid)
return getSum(T->lchild,x,y);
else if(x>mid)
return getSum(T->rchild,x,y);
else if(x<=mid&&y>mid)
return getSum(T->lchild,x,mid)+getSum(T->rchild,mid+1,y);
}
//获得区间[x,y]的最大值
int getMax(LiTree &T,int x,int y)
{
int mid=(T->left+T->right)/2;
if(T->left==x&&T->right==y)
{
return T->max;
}
else if(y<=mid)
return getMax(T->lchild,x,y);
else if(x>mid)
return getMax(T->rchild,x,y);
else if(x<=mid&&y>mid)
return getMax(T->lchild,x,mid)>getMax(T->rchild,mid+1,y)?getMax(T->lchild,x,mid):getMax(T->rchild,mid+1,y);
}