线段树

来源:互联网 发布:知乎眼睛漂亮的女孩 编辑:程序博客网 时间:2024/06/14 19:56


<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刚开始我们传值为1a,ba~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:此时直接返回。

*/

原创粉丝点击