线段树

[定义]

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

[作用]

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。

(以上的全部都来源于百度)

[题目描述]

小E和小G是好朋友,小E非常喜欢吃甜食,一天小G买回了他最爱吃的Good News牌巧克力松露、咖啡奶酪蛋糕、菠萝心的姜饼,还有苹果派、牛轧糖、樱桃奶油和椰子软糖。小E将这N个甜品摆成一排,他将会吃掉一些放在一起的甜品。但是小G告诉他,如果巧克力吃得太多,牙齿就会坏掉。每一种甜品都有一定的糖份值,如果它们的糖份值加起来不超过一个值X,那么小E就可以吃掉这些甜品而不会得蛀牙。作为小E的朋友,小G决定帮小E判断他是否可以吃掉这些甜品。但是注意在这个过程中,小E可能会在一些甜品上撒上糖霜,这些甜品的糖份值就会增加。

[输入格式]

第一行输入三个正整数n、m 和x。分别代表甜品数量,操作数量和最大允许的糖份值。
第二行有n 个正整数,代表每一个甜品的糖份值Ai 。
接下来m 行,每一行包含一个问题或一个操作。
“1 a b”代表询问[a,b]这个区间内的糖份值是否超过x(1≤a,b≤n)。
“2 a b c”代表[a,b]这个区间内的甜品糖份值加上c(1≤a,b≤n,0≤c≤1000)。

[输出格式]

对于每一个询问,如果糖份值不超过x,那么输出“yes”,反之输出“no”。

[样例数据]

Sample Input
3 3 5
1 3 4
1 1 2
2 2 3 1
1 1 3

Sample Output
yes
no

[解题方法]

当然是线段树啊!(教练说只要是区间和的问题大部分都能用线段树)
(图片来源于这位洛谷的神犇)
看图再结合下面的代码会很好理解。
线段树的二分是按区间的范围来分的!!!

[如何实现]

说实话我的线段树也很烂,不知为什么有勇气在这发教程

  • 主函数

(从主函数开始阅读是一个好习惯!!!)

int main(void)
{
	scanf("%lld %lld %lld",&n,&m,&x);
	long long int i;
	for(i=1;i<=n;i++)
	{
		scanf("%lld",&s[i]);
	}
	build(1,n,1);//建立一个二叉树
	for(i=1;i<=m;i++)
	{
		scanf("%lld",&t);
		if(t==1)
		{
			flag=1;//flag用来判断是否可以吃,可以的话就是1,否则就会在后面变为0;
			sum=0;
			scanf("%lld %lld",&a,&b);
			query(1,n,1);//用来查询区间;
			if(flag==1)
			{
				printf("yes\n");
			}
			else
			{
				printf("no\n");
			}
		}
		else
		{
			scanf("%lld %lld %lld",&a,&b,&c);
			update(1,n,1,c);//用来更新区间的值;
		}
	}
	return 0;
}

主函数很清晰吧。

  • 树的构造

void build(long long int l,long long int r,long long int k)
{
	if(l==r)//如果l==r,说明已经到了树的底部,不可再延伸下去,就储存值后返回;
	{
		tree[k]=s[l];
		return;//必须要返回,不然会继续延展下去!!!
	}
	long long int mid=(l+r)/2;
	build(l,mid,k*2);
	build(mid+1,r,k*2+1);
	tree[k]=tree[k*2]+tree[k*2+1];//这步很重要;
	return;
}
  • 区间查询

void query(long long int l,long long int r,long long int k)
{
	
	if(flag==0)return;
	if(a<=l&&r<=b)//如果判断该区间在要(yào)求的区间内,就把糖分的总值sum加上该区间的总值;
	{
		sum=sum+tree[k];
		if(sum>x)flag=0;//这是flag改变的地方;
		return;//记住返回!!!
	}
	if(lazy[k])pushdown(l,r,k);//看到这里疑惑的OIer不要管,这是lazy标记,马上会提到;
	long long int mid=(l+r)/2;
	if(a<=mid)query(l,mid,k*2);//如果l~mid的区间包含了要(yào)求的区间中的一部分,就进入,这样可以节约时间;
	if(mid+1<=b)query(mid+1,r,k*2+1);//同理;
	return;
}
  • lazy

lazy标记是干嘛的呢?首先,如果我们进行区间修改时一次一次修改,复杂度一定会极其高(好吧其实也不是特别高)。于是聪明的&@.#!~_+*就选择用在区间上标记的方法,然后载沉淀下去,就会变得很快了。
下面就是“lazy”(琪亚娜) 时间!!

  • 修改区间

void f(long long int l,long long int r,long long int k,long long int p)
{
	tree[k]=tree[k]+p*(r-l+1);//把下沉到的区间加上区间内数的数目乘lazy(即p);
	lazy[k]=lazy[k]+p;//把lazy加上p,因为下沉还没结束,一个区间改变会影响它下面的多个区间;
	return;
}
void pushdown(long long int l,long long int r,long long int k)
{
	long long int mid=(l+r)/2;
	f(l,mid,k*2,lazy[k]);//这是把lazy下沉的函数;
	f(mid+1,r,k*2+1,lazy[k]);//同上;
	lazy[k]=0;//因为lazy[k]已经下沉给了l~mid(tree[k*2])&mid+1~r(tree[k*2+1])两段区间,所以lazy[k]要清零;
	return;
}
void update(long long int l,long long int r,long long int k,long long int p)//请从这开始阅读!
{
	if(a<=l&&r<=b)//如果该区间在需要改变的区间范围内,把这个树节点加上区间内数的数目乘需要改变的大小的积,并把lazy[k]加上需要改变的值,以便沉淀的时候使用;
	{
		lazy[k]=lazy[k]+p;
		tree[k]=tree[k]+p*(r-l+1);
		return;//记住返回!否则会陷入死循环(作者亲测);
	}
	pushdown(l,r,k);//就是上面多次说的沉淀;
	long long int mid=(l+r)/2;
	if(a<=mid)update(l,mid,k*2,p);//如果l~mid的区间包含了需要修改的区间中的一部分,就进入,这样可以节约时间;
	if(mid+1<=b)update(mid+1,r,k*2+1,p);//同理;
	tree[k]=tree[k*2]+tree[k*2+1];//这句必须有!否则有些区间会无法更新到;
	return;
}

[完整代码]

#include<cstdio>
#include<iostream>
using namespace std;
long long int n,m,x,a,b,flag;
long long int s[100010],tree[400010],lazy[400010],sum;
void build(long long int l,long long int r,long long int k)
{
	if(l==r)
	{
		tree[k]=s[l];
		return;
	}
	long long int mid=(l+r)/2;
	build(l,mid,k*2);
	build(mid+1,r,k*2+1);
	tree[k]=tree[k*2]+tree[k*2+1];
	return;
}
void f(long long int l,long long int r,long long int k,long long int p)
{
	tree[k]=tree[k]+p*(r-l+1);
	lazy[k]=lazy[k]+p;
	return;
}
void pushdown(long long int l,long long int r,long long int k)
{
	long long int mid=(l+r)/2;
	f(l,mid,k*2,lazy[k]);
	f(mid+1,r,k*2+1,lazy[k]);
	lazy[k]=0;
	return;
}
void update(long long int l,long long int r,long long int k,long long int p)
{
	if(a<=l&&r<=b)
	{
		lazy[k]=lazy[k]+p;
		tree[k]=tree[k]+p*(r-l+1);
		return;
	}
	pushdown(l,r,k);
	long long int mid=(l+r)/2;
	if(a<=mid)update(l,mid,k*2,p);
	if(mid+1<=b)update(mid+1,r,k*2+1,p);
	tree[k]=tree[k*2]+tree[k*2+1];
	return;
}
void query(long long int l,long long int r,long long int k)
{
	
	if(flag==0)return;
	if(a<=l&&r<=b)
	{
		sum=sum+tree[k];
		if(sum>x)flag=0;
		return;
	}
	if(lazy[k])pushdown(l,r,k);
	long long int mid=(l+r)/2;
	if(a<=mid)query(l,mid,k*2);
	if(mid+1<=b)query(mid+1,r,k*2+1);
	return;
}
int main(void)
{
	scanf("%lld %lld %lld",&n,&m,&x);
	long long int i,t,c;
	for(i=1;i<=n;i++)
	{
		scanf("%lld",&s[i]);
	}
	build(1,n,1);
	for(i=1;i<=m;i++)
	{
		scanf("%lld",&t);
		if(t==1)
		{
			flag=1;
			sum=0;
			scanf("%lld %lld",&a,&b);
			query(1,n,1);
			if(flag==1)
			{
				printf("yes\n");
			}
			else
			{
				printf("no\n");
			}
		}
		else
		{
			scanf("%lld %lld %lld",&a,&b,&c);
			update(1,n,1,c);
		}
	}
	return 0;
}

Q:看懂了吗?
A:反正我自己是看不懂的hhh
<=to be continued

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值