蓝桥杯——操作格子

问题描述
有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);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值