题目:
有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
在这里写一写主要还是为了凝练凝练自己思路,再好好体会体会~~
分析:
一般人的一般做法:
修改格子权值可以直接定位,但是再求一段连续的格子之和及最大值都会采用for循环,可以好好想一想,n取100000,m取100000,那么n*m为10^10,为10亿,但是计算机1s只能处理1亿的数据可见明显超时。
代码如下:
#include<iostream>
using namespace std;
int n, m, a[100000];
void Operate1(int x, int y);
int Operate2(int x, int y);
int Operate3(int x, int y);
int main()
{
int i, p, x, y, result;
cin>>n>>m;
for(i = 0; i < n; i++)
cin>>a[i];
i = 1;
while(i <= m)
{
cin>>p>>x>>y;
if(p == 1)
Operate1(x, y);
if(p == 2){
result = Operate2(x, y);
printf("%d\n", result);
}
if(p == 3){
result = Operate3(x, y);
printf("%d\n", result);
}
i++;
}
return 0;
}
void Operate1(int x, int y)
{
a[x-1] = y;
}
int Operate2(int x, int y)
{
int sum = 0;
for(x = x-1; x < y; x++)
sum += a[x];
return sum;
}
int Operate3(int x, int y)
{
int max = a[x-1];
for(x = x; x < y; x++)
if(max < a[x])
max = a[x];
return max;
}
但是再蓝桥网上有很明显提示"线段树",刚开始还不知道什么是线段树,看了下别人的博客才清晰明白,在这里可看线段树入门,花一些时间应该可以弄懂。
首先是需要建立一个空树即树上的结点是没有被赋值的例如:
这棵树开始为1-4,(1+4)/2 = 2.5取整为2,所以左边区间变成了1-2,右边区间变成了3-4,再分成了单个结点1、2、3、4,节点数目多一些也是如此。
我们一个表格来描述:
根据表格,这里一看就比较清晰了,求sum, max只需在对应的区间找就行了,但是改变权值呢,其实还是要用递归求解的,在这一题中,需要对树的基本操作很熟才行,每一个函数都用到了树的基本操作并在基本操作的基础上加大难度,更都离不开递归,所以…《数据结构》学好。
XNode *CreatXTree(int left, int right)
{
XNode *xTree = (XNode *)malloc(sizeof(XNode));
//线段树区间的左右值
xTree->left = left;
xTree->right = right;
//线段树:结点维护内容也就是初始化
xTree->max = 0;
xTree->sum = 0;
xTree->lchild = NULL;
xTree->rchild = NULL;
if(right != left)//单元区间
{
//对分
int mid = (left+right)/2;
xTree->lchild = CreatXTree(left, mid);
xTree->rchild = CreatXTree(mid+1, right);
}
return xTree;//可以理解为根节点
}
这是创建空树的函数,此时给对应的结点按区间对分在分别遍历左子树及右子树并初始化xTree->max = 0,xTree->sum = 0,返回类型为指针型的根结点。
void Insert(XNode *xTree, int point, int value)
{
xTree->sum += value;
xTree->max = maxValue(xTree->max, value);
if(xTree->left == xTree->right)//找到了对应的结点
return ;
else
{
if(point <= (xTree->left+xTree->right)/2)
Insert(xTree->lchild, point, value);//左搜索
else
Insert(xTree->rchild, point, value);//右搜索
}
return ;
}
这个函数是执行插入操作,即给对应的每个结点或者说线段进行赋值,边赋值边统计sum之和即结点的max,xTree->sum += value,xTree->max = maxValue(xTree->max, value),分别进行左搜索和右搜索并按照结点序号point分配到所规定的编号之中即可。
void Modify(XNode *xTree, int point, int value)
{
if(xTree->left == point&&xTree->right == point)//找到该结点,修改
{
xTree->max = value;//针对本结点而言
xTree->sum = value;
return ;
}
else
{
int mid = (xTree->left+xTree->right)/2;
if(point <= mid)
Modify(xTree->lchild, point, value);
else
Modify(xTree->rchild, point, value);
xTree->max = maxValue(xTree->lchild->max, xTree->rchild->max);//自下向上
xTree->sum = xTree->lchild->sum+xTree->rchild->sum; //自下向上,重新修改
}
return ;
}
这个函数很重要,首先必须找到该结点,修改xTree->max = value,xTree->sum = value,这是针对它本身而言的,其次,一动则上面的sum,max都得动,所以自下向上,对xTree->sum实现加和,xTree->求其最大值即可。
完整代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
//构造一个线段树结构体
typedef struct node{
int max, sum;//用于统计线段树的最大值及和
int left, right;//线段树区间的左右值即x,y
struct node *lchild;//左子树
struct node *rchild;//右子树
}XNode;
XNode *CreatXTree(int left, int right);
void Insert(XNode *xTree, int point, int value);
int maxValue(int max, int value);
void Modify(XNode *xTree, int point, int value);
int GeziSum(XNode *Tree, int left, int right);
int GeziMax(XNode *Tree, int left, int right);
int main()
{
int m, n, i, j;
XNode *xTree = NULL;//空树
int input[100000][3];//input[][0]:操作序号1、2、3,input[][1]:x,input[][2]:y
int Gezi;
cin>>n>>m;
xTree = CreatXTree(1, n);//创建线段树
for(i = 1; i <= n; i++)
{
cin>>Gezi;
Insert(xTree, i, Gezi);//给线段树的对应的结点赋值
}
for(i = 0; i < m; i++)
for(j = 0; j < 3; j++)//循环三次0、1、2
cin>>input[i][j];
//执行操作
for(i = 0; i < m; i++)
{
switch(input[i][0])
{
case 1:Modify(xTree, input[i][1], input[i][2]);break;//修改权值<一个代表序号,另一个代表权值
case 2:printf("%d\n", GeziSum(xTree, input[i][1], input[i][2]));break;
case 3:printf("%d\n", GeziMax(xTree, input[i][1], input[i][2]));break;
default:break;
}
}
return 0;
}
XNode *CreatXTree(int left, int right)
{
XNode *xTree = (XNode *)malloc(sizeof(XNode));
//线段树区间的左右值
xTree->left = left;
xTree->right = right;
//线段树:结点维护内容也就是初始化
xTree->max = 0;
xTree->sum = 0;
xTree->lchild = NULL;
xTree->rchild = NULL;
if(right != left)//单元区间
{
//对分
int mid = (left+right)/2;
xTree->lchild = CreatXTree(left, mid);
xTree->rchild = CreatXTree(mid+1, right);
}
return xTree;//可以理解为根节点
}
void Insert(XNode *xTree, int point, int value)
{
xTree->sum += value;
xTree->max = maxValue(xTree->max, value);
if(xTree->left == xTree->right)//找到了对应的结点
return ;
else
{
if(point <= (xTree->left+xTree->right)/2)
Insert(xTree->lchild, point, value);//左搜索
else
Insert(xTree->rchild, point, value);//右搜索
}
return ;
}
int maxValue(int max, int value)
{
if(max < value)
max = value;
return max;
}
void Modify(XNode *xTree, int point, int value)
{
if(xTree->left == point&&xTree->right == point)//找到该结点,修改
{
xTree->max = value;//针对本结点而言
xTree->sum = value;
return ;
}
else
{
int mid = (xTree->left+xTree->right)/2;
if(point <= mid)
Modify(xTree->lchild, point, value);
else
Modify(xTree->rchild, point, value);
xTree->max = maxValue(xTree->lchild->max, xTree->rchild->max);//自下向上
xTree->sum = xTree->lchild->sum+xTree->rchild->sum; //自下向上,重新修改
}
return ;
}
int GeziSum(XNode *xTree, int left, int right)
{
if(left == xTree->left&&right == xTree->right)
return xTree->sum;
else
{
int mid = (xTree->left+xTree->right)/2;
//以返回值为操作,实质为"和"值服务
if(right <= mid) //往左子树搜索
return GeziSum(xTree->lchild, left, right);
else if(left > mid) //往右子树搜索
return GeziSum(xTree->rchild, left, right);
else //分叉搜索,左右搜索"和"值
return GeziSum(xTree->lchild, left, mid)+GeziSum(xTree->rchild, mid+1, right);
}
}
int GeziMax(XNode *xTree, int left, int right)
{
if(left == xTree->left&&right == xTree->right)
return xTree->max;
else
{
int mid = (xTree->left+xTree->right)/2;
if(right <= mid) //往左子树搜索
return GeziMax(xTree->lchild, left, right);
else if(left > mid) //往右子树搜索
return GeziMax(xTree->rchild, left, right);
else //寻求二值之间的最大值
return maxValue(GeziMax(xTree->lchild, left, mid), GeziMax(xTree->rchild, mid+1, right));
}
}
我认为对于我这种初级选手而言,这一题可谓让我大开眼界。