蓝桥杯算法提高——分苹果(线段树)+图解

分苹果(线段树)

原题连接
https://www.dotcpp.com/oj/problem1501.html
这一题用线段树可以ac,我第一次做的时候也没往这想直接带着键盘就敲,不出所料直接时间超限(狗头)。
然后细细想来感觉不简单,不愧是算法提高题。这就要拿出线段树来镇场子。
原题:
小朋友排成一排,老师给他们分苹果。
小朋友从左到右标号1…N。有M个老师,每次第i个老师会给第Li个到第Ri个,一共Ri-Li+1个小朋友每人发Ci个苹果。
最后老师想知道每个5小朋友有多少苹果。

输入

5 3
1 2 1
2 3 2
2 5 3

输出

1 6 5 3 3

先来说说线段树的基本框架:

void function(node,left,right,...(其他参数)//node是当前结点索引,left是当前节点代表区间的左区间,right是当前节点代表区间的右区间
{
	对树进行的操作

	mid=(left+right)/2;//中间节点
	function(left,mid);//左子树递归
	function(mid+1,right);//右子树递归
	return;
}

让我们看一看ac代码,你会发现所涉及到关于线段树的操作都是基于这个框架。
猛一看下面的代码加上注释密密麻麻,不要害怕,都是一步一步走过来的。当初我做题找题解时随便点进去一个题解,一看密密麻麻的都是直接退出,然后下一个。。到头来还是老老实实一点一点啃。

#include <iostream>
using namespace std;
int tree[400004];//一般使用线段树都是至少开辟4倍空间
//建树
void buildtree(int node,int left,int right){
	if(left==right){//对叶子节点赋值:1==1,2==2,3==3,4==4,5==5.
		tree[node]=0;
		return;
	}
	int mid=(left+right)>>1;//mid是[left,right]的中间节点,>>1代表除于2,相比较/2更快。<<1代表乘2
	buildtree(node<<1,left,mid);//建左子树
	buildtree(node<<1|1,mid+1,right);//建右子树,node<<1|1:代表node*2+1;
	tree[node]=0;//非叶子节点赋值
} 

//按照给的区间加苹果。
void addapple(int node,int left,int right,int l,int r,int count){
	//列举所给区间[l,r]和当前节点所代表区间[left,right]的关系
	if(l<=left&&right<=r){//关系1:当前节点所带代表的区间小区所给区间,直接将苹果加到这个节点不需要再往下找具体的叶子节点(后面会有图解)
		tree[node]+=count;
		return;
	}
	if(left>r||right<l){//关系2:所给节点与当前节点所代表的区间不相交,不用再进行操作,直接返回。
		return ;
	}
	if(right==left){//加到叶子节点上
		tree[node]+=count;
		return;
	}
	int mid=(left+right)>>1;
	if(mid>=l)addapple(node<<1,left,mid,l,r,count);//同样递归左右子树
	if(mid<r)addapple(node<<1|1,mid+1,right,l,r,count);
}

//打印结果
void printtree(int node,int left,int right){
	if(left==right){//打印每个学生手中的苹果
		cout<<tree[node]<<" ";
		return;
	}
	int mid=(left+right)>>1;
	tree[node<<1]+=tree[node];//当前节点的值加到他的左孩子上。
	tree[node<<1|1]+=tree[node];//当前节点的值加到他的右孩子上。
	printtree(nextnode,left,mid);//左右子树递归
	printtree(nextnode+1,mid+1,right);
}

int main(){
	int n,m;
	int li,ri,ci; 
	cin>>n>>m;//n学生,m老师;
	buildtree(1,1,n);//建树
	for(int i=1;i<=m;i++){
		cin>>li>>ri>>ci;//[li,ri]老师发苹果的区间,ci老师分发苹果的数量
		addapple(1,1,n,li,ri,ci);
	}
	printtree(1,1,n);
}

每个函数的图示
1.建树

建完树后
在这里插入图片描述
(本来理想是弄个动态图更加直观,奈何本人不会啊。。)
叶子节点:代表学生,叶子节点的大框中红色的数字代表的就是学生的编号。下面小方框中的数字代表的是每个学生手中的苹果。
建完树后每个学生手中的苹果数量都是0

建树中体现出来的框架

void buildtree(int node,int left,int right){
	if(left==right){//这是对线段树的操作,这个函数的功能是对节点赋值所以是赋值操作,这是对叶子节点进行赋值
		tree[node]=0;
		return;
	}
	
	int mid=(left+right)>>1;//这是对线段树左右子树进行递归
	buildtree(node<<1,left,mid);
	buildtree(node<<1|1,mid+1,right);
	
	tree[node]=0;//这是对非叶子节点赋值
	//这个赋值想不明白的。可以自己举个特殊的例子比如就三个节点的树:
	       1~2
	     /    \
	    1      2
} 

2.给学生加苹果
输入
5 3
1 2 1
2 3 2
2 5 3
执行完1 2 1后的状态
在这里插入图片描述执行完2 3 2后的状态
在这里插入图片描述
执行完2 5 3后的状态
在这里插入图片描述

图中蓝色就是操作后的变化;初学线段树的朋友可以想一想为什么这3个苹果直接加到2号和3号同学,而4号和5号同学却加到他们的父亲节点上。

框架的体现

void addapple(int node,int left,int right,int l,int r,int count){
	//列举所给区间[l,r]和当前节点所代表区间[left,right]的关系
	
	//这三个if就是对线段树进行的操作,
	//这个函数的功能是对所给区间[l,r]进行加count个苹果的操作。
	//所以你看这三个if里面的语句都是对节点进行相加的操作
	if(l<=left&&right<=r){//关系1:加到父亲节点上
		tree[node]+=count;
		return;
	}
	if(left>r||right<l){//关系2:所给节点与当前节点所代表的区间不相交,不用再进行操作,直接返回。
		return ;
	}
	if(right==left){//加到叶子节点上
		tree[node]+=count;
		return;
	}
	
	//对线段树左右子树进行递归
	int mid=(left+right)>>1;
	if(mid>=l)addapple(node<<1,left,mid,l,r,count);//同样递归左右子树
	if(mid<r)addapple(node<<1|1,mid+1,right,l,r,count);//node<<1|1:代表node*2+1;
}

3.打印结果
打印第一个学生
在这里插入图片描述打印第二个学生
在这里插入图片描述
以此类推全部打印。
框架的体现

void printtree(int node,int left,int right){
	if(left==right){//这个函数对线段树的操作是打印,故打印每个学生手中的苹果
		cout<<tree[node]<<" ";
		return;
	}
	int mid=(left+right)>>1;
	tree[node<<1]+=tree[node];//当前节点的值加到他的左孩子上。
	tree[node<<1|1]+=tree[node];//当前节点的值加到他的右孩子上。
	
	//对线段树左右子树进行递归
	printtree(nextnode,left,mid);
	printtree(nextnode+1,mid+1,right);
}

总结一下线段树的框架函数就是:

  • 相关操作:这个需要根据自己的需求,就比如这一题的需求是:建树,给学生添加苹果,打印节点。
  • 左右子树的递归

注:学习算法本来就不是一蹴而就的事情,刚开始看别人的代码都是头大无从下手。建议多看几遍,多想想。共勉!
对本文有什么疑问或建议直接评论或者私信即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值