格子操作(线段树)

题目:
有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));
	}	
} 

        我认为对于我这种初级选手而言,这一题可谓让我大开眼界。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值