线段树学习笔记

 线段树学习总结

    这个星期主要是进行线段树的复习与进一步学习,做了些题目,将我的错误与注意事项总结在此。

    在这次的学习中,我才算是基本会使用了线段树,不再像以前,只能当做一种帮忙整段修改区间,求解区间最大值的数据结构,线段树的优越就在于它的树形结构,将任何操作都降到了log级别的,在此基础上可以记录很多数据,而并非只有节点的值。

 

我的线段树习惯如下:

此种写法参考了http://www.notonlysuccess.com/index.php/segment-tree-complete 

1, 用root表示当前节点,不记录节点区间,而是在递归过程中随着函数的改变自动更新。

2, PushUp()  是把当前结点的信息更新到父结点

3, PushDown()是把当前结点的信息更新给子节点

4, 要更改的区间用全局变量x,y记录下,不在递归的过程中传递

 

做了些题目,将其中2道说下 :

POJ 3468

    成段增减,区间求和问题

    要注意的数据范围,要使用longlong来存储,另外需要注意的是用scanf()读入long long的时候还是要用“%I64d”,因为会有负数的出现,在正整数的int范围内使用什么都无所谓。

  下边是部分的代码

POJ 3468过程
 1 //更新结点信息,即加减处理
 2 void updata(int l,int r,int root)
 3 {
 4     if ((y<l)||(x>r)) return;
 5     
 6     if ((x<=l)&&(y>=r))
 7     {
 8         a[root][0]+=c*(r-l+1);
 9         a[root][1]+=c;
10         return;
11     }
12     
13     int mid=(l+r)/2;
14     //必须是临时变量,避免递归时改变
15     
16     //left child
17     a[root*2][0]+=a[root][1]*(mid-l+1); 
18     a[root*2][1]+=a[root][1];
19     //right child
20     a[root*2+1][0]+=a[root][1]*(r-mid);
21     a[root*2+1][1]+=a[root][1];
22     //itself
23     a[root][1]=0;
24     
25     updata(l,mid,root*2);
26     updata(mid+1,r,root*2+1);
27     //change Max
28     a[root][0]=a[root*2][0]+a[root*2+1][0];
29 }
30 
31 //寻找区间累加和
32 int find(int l,int r,int root)
33 {
34     if ((y<l)||(x>r)) return 0;
35     if ((x<=l)&&(y>=r)) return a[root][0];
36     
37     int mid=(l+r)/2;
38     //left child
39     a[root*2][0]+=a[root][1]*(mid-l+1); 
40     a[root*2][1]+=a[root][1];
41     //right child
42     a[root*2+1][0]+=a[root][1]*(r-mid);
43     a[root*2+1][1]+=a[root][1];
44     //itself
45     a[root][1]=0;
46     
47     int m1=find(l,mid,root*2);
48     int m2=find(mid+1,r,root*2+1);
49     
50     return m1+m2;
51 }

 

POJ 3667

    要求解的是寻找长度为len的最靠左边的一段区间,将其占据,或者将一段长度的区间释放。

    这次线段树要记录的就不是结点的值了,而是区间内的一些特殊的值,如下所示

1 struct tree
2 {
3     int l;    //从左端点开始的连续最大长度
4     int r;    //以右端点结束的连续最大长度
5     int sum;    //区间内最大连续长度
6     int cover;    //区间状态(是否覆盖 : 0,1,-1)
7 } a[maxn*4];

  cover域的数字,0表示全为空的,1表示全被占了,-1为其他情况,具体的思路见代码注释

POJ 3667
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=80100;

struct tree
{
    int l;        //从左端点开始的连续最大长度
    int r;        //以右端点结束的连续最大长度
    int sum;        //区间内最大连续长度
    int cover;    //区间状态(是否覆盖 : 0,1,-1)
} a[maxn*4];

int n,m,color,len,x,y;

//初始化,每段空房间数为r-l+1
void Build(int l,int r,int root)
{
    a[root].l=a[root].r=a[root].sum=r-l+1;
    a[root].cover=0;
    if (l==r) return;
    int mid=(l+r)/2;
    Build(l,mid,root*2);
    Build(mid+1,r,root*2+1);
}

void PushDown(int l,int r,int root)
{
    //Updata children's state
    if (a[root].cover!=-1)
    {
        a[root*2].cover=a[root*2+1].cover=a[root].cover;
        int mid = (l+r)/2;
        a[root*2].l=a[root*2].r=a[root*2].sum = a[root].cover? 0 : mid-l+1;
        a[root*2+1].l=a[root*2+1].r=a[root*2+1].sum = a[root].cover? 0 : r-mid;
        a[root].cover=-1;
    }    
}

//updata father
void PushUp(int l,int r,int root)
{
    a[root].l = a[root*2].l;
    a[root].r = a[root*2+1].r;
    int mid = (l+r)/2;
    if (a[root].l==mid-l+1) a[root].l += a[root*2+1].l;
    if (a[root].r==r-mid) a[root].r += a[root*2].r;
    a[root].sum = max(a[root*2].r+a[root*2+1].l,max(a[root*2+1].sum,a[root*2].sum));
}

void updata(int l,int r,int root)
{
    if (y<l||x>r) return;
    
    if (x<=l&&y>=r)
    {
        a[root].sum=a[root].l=a[root].r = color? 0 : r-l+1;
        a[root].cover=color;
        return;
    }
    PushDown(l,r,root);
    
    int mid = (l+r)/2;
    updata(l,mid,root*2);
    updata(mid+1,r,root*2+1);
    
    PushUp(l,r,root);
}

int query(int l,int r,int root)
{
    //特殊情况
    if (l==r) return l;

    PushDown(l,r,root);
    
    int mid = (l+r)/2;
    
    //按照顺序求解答案,要求的是靠左的优先
    if (a[root*2].sum>=len) return query(l,mid,root<<1);
    else if (a[root*2].r+a[root*2+1].l>=len) return mid-a[root*2].r+1;
    
    return query(mid+1,r,root*2+1);
    /*
        关于这段我也是理解了半天,如果左节点的最大长度正好的话就进入递归
        在递归里面对这段进行计算,满足了l+r,即第二个条件,会输出正确答案也就是当前的l
    */    
}

int main()
{
    freopen("hotel.in","r",stdin);
    freopen("hotel.out","w",stdout);
    
    scanf("%d%d",&n,&m);
    Build(1,n,1);
    
    int tmp;
    for (int i=1; i<=m; i++)
    {
        scanf("%d",&tmp);
        if (tmp==1) 
        {
            scanf("%d",&len);
            //find the sum>=len
            int p;
            if (a[1].sum<len) p=0;
            else p = query(1,n,1);
            printf("%d\n",p);
            x=p; y=p+len-1; color=1;
            if (p) updata(1,n,1);
        }
        else
        {
            scanf("%d%d",&x,&len);
            y=x+len-1; color=0;
            updata(1,n,1);
        }
    }
    
    
    fclose(stdin); fclose(stdout);
    return 0;
}

 

学到最后,感想就是线段树就是一个log级别的数据结构,跟区间有关的很多值都可以记录下来然后进行高效的操作,总的来说,就是分成两个操作,修改和查询,具体的区别根据实际的需要我们可以进行具体操作,我学到的可以进行【单点更新,成段更新,区间合并,扫描线】,遇到这类的数据结构,首先要考虑能不能使用线段树求解了。

 

 

 

 

posted @ 2012-04-28 21:14  守護N1身边  阅读(212)  评论(0编辑  收藏  举报