<1>线段树
解决的问题:多数类似于线段类的问题,成段出现;
线段树基本主成:线段树的建立、添加、删除、替换、求和、更新值。
Struct node
{
int l,r,sum;
};
A. 线段树的建立+建立和
思路:建立结构体,存放左右子树、所在段的和;通过递归的方式,依次搜索左右子树,同时我还记录每一层的段和;
前提:用一个数组存编号;num[i]=i;
Builr(1,1,n);
void Build(int i,int l,int r)
{
p[i].l=l;p[i].r=r;
if(l==r)
{
p[i].sum=num[l];
return ;
}
int mid=(r+l)>>1;
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
p[i].sum=p[i<<1].sum+p[i<<1|1].sum;
}
B. 添加
需要在p1这个段添加d;
思路:我还是同样从最顶点出发,寻找p1的位置,当然,我每到一段我就要把我这段+d;
因为一个值的变化影响了上一层的变化,接着又会影响上上一层的变化。
Add(1,p,d);
Add(int i,int p1,int d)
{
sum[i].sum+=d;
if(p1==p[i].r&&p1==p[i].l)
return ;
intmid=(p[i].r+p[i].l)>>1;
if(p1<=mid) Add(i<<1,p1,d);
else Add(i<<1|1,p1,d);
}
C. 线段和
在求某一段的和时,比如给我一段1~n的总长,构成线段树后,再让你求3~6之间段的总和,
那我是不是先要写一个函数:long long Query(int i,int l,int r);
i:就是指当前的所在位置,每一次我肯定是从顶点开始往下搜,
所以i刚开始我们传值为1,a,b指a~b我们需要求的这段线段的和的区间:Query(1,a,b);
intQuery(int I=i,int l,int r)
{
if(p[i].r==r&&p[i].l==l)
return p[i].sum;
int mid=(l+r)>>1;
if(r<=mid)
return Query(i<<1,l,r);
else if(l>mid) return Query(i<<1|1,l,r);
else
returnQuery(i<<1,l,mid)+Query(i<<1|1,mid+1,r);
}
首先
我要判断if(l==p[i].l&&r==p[i].r) return p[i].sum;即可;
否则我继续向下搜,int mid=(l+r)>>1;
关键点就是我要判断这个区间的范围在哪?
1).全都在我的左子树的区间;
2).要么全在我的右子树中;
3).左右子树都有分布;
so,这三个条件是互斥的,也就是说,根本不会有交叉点,即,int mid=(r+l)>>1;
if(r<=mid)
return Query(i<<1,l,r);全在左子树中
else if(l>mid)
return Query(i<<1|1,l,r);全在右子树中
else
return Query(i<<1,l,mid)+Query(i<<1|1,mid+1,r);
值得注意的是:
1).当全在左子树或右子树时,我向下寻找时,左右范围依然是自己;
而在左右子树分别均有分布时,左子树Query(i<<1,l,mid),右子树是Query(i<<1|1,mid+1,r);
2).结束条件:
if(l==p[i].l&&r==p[i].r) return p[i].sum;//意为我刚好找到这个区间;
<4>更新值
我需要在结构体中加一个成员add;
struct node
{
int r,l,add,sum;
}p[Max];
我在建树的同时也要每一段的add初始化为0;
简单的写一下代码:
Build(1,1,n);
void Build(int i,intl,int r)
{
p[i].add=0;
p[i].l=l;
p[i].r=r;
if(l==r)
{
p[i].sum=num[l];return ;
}
int mid=(l+r)>>1;
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
p[i].sum=p[i<<1].sum+p[i<<1|1].sum;
}
假如说,我将区间a~b的所有值换成d;
update(1,a,b,add);
void update(int i,int l,int r,int d)
{
if(p[i].l==l&&p[i].r==r)
{
p[i].add=d;
p[i].sum=(r-l+1)*d;
return ;
}
if(p[i].r==p[i].l)
return ;
if(p[i].add)
{
intk=p[i].r-p[i].l+1;
p[i<<1].add=p[i].add;
p[i<<1|1].add=p[i].add;
p[i<<1|1].sum=p[i].add*(k>>1);
p[i<<1].sum=p[i].add*(k-(k>>1));
p[i].add=0;
}
intmid=(l+r)>>1;
if(r<=mid)
update(i<<1,l,r,d);
elseif(l>mid)
update(i<<1|1,l,r,d);
else
{
update(i<<1,l,mid,d);
update(i<<1|1,mid+1,r,d);
}
p[i].sum=p[i<<1].sum+p[i<<1|1].sum;
}
/*
注意:
1).带括号,运算符的优先级(‘-’优先级大于’>>’);
2).向左子树搜索时:
p[i<<1].sum=p[i].add*(k>>1);
向右子树搜索时:
p[i<<1|1].sum=p[i].add*(k-(k>>1));
3).k=p[i].r-p[i].l+1;指线段的元素个数
4).同时最后p[i].add=0;p[i].add需要清零,因为这一段不一定是我们要替换的段,
5).当然,在左右子树都已经确定的情况下,我们别忘记对父线段进行求和,即:
p[i].sum=p[i<<1].sum+p[i<<1|1].sum;
6).这里有两个返回条件:
1>当我刚好找那个区间时:
即:p[i].l==l&&p[i].r==r;
我加入d,同时p[i].sum=(r-l+1)*d;p[i].add=d;return ;
2>我找到底了l==r:此时直接返回。
*/