[定义]
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
[作用]
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为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”(琪亚娜) 时间!!
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