zoukankan      html  css  js  c++  java
  • 线段树

    一 综述

    线段树是一种类似与二叉搜索树的结构,及非叶子节点一定包含了左右子树。每个节点存储了一个区间(线段)的值(可以是最值,区间和等)。所以对于这个节点,需要的信息应该包括

    该节点表示的连续区间l,以及该节点的数据。

    我们可以用线段树来做什么呢?线段树可以在log(n)的时间内实现区间修改(单点修改)和区间查询。其操作的原理就是我们将区间表示成几个连续的小区间,通过对这几个连续小区间的操作

    实现对我们想要操作节点的操作。(可以证明一定可以由log(n)*2个区间来表示)

    二 线段树的常用操作

    定义

    #define maxn 100007
    int sum[maxn<<2],add[maxn<<2]; //sum表示区间和,add是懒惰标记
    int a[maxn],n;//a数组存储原数据 下标1-n

    建树

    void build(int p,int l,int r)
    {
        if(l==r) //递归到了叶子节点
        {
            sum[p]=a[l];
            return;
        }
        int m=(l+r)>>1;
        //递归左右子树
        build(p<<1,l,mid);
        build(p<<1|1,mid+1,r);
        
        PushUp(p);
    }

    维护区间和

    void PushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }

    单点更新: 将某个节点的值进行更新。则从该节点到根节点的路径上的节点都需要更新

    void update(int L,int C,int l,int r,int p) //L是要更新的结点,C是值,p是当前的节点
    {
        if(l==r)
        {
            sum[p]+=C;
            return;
        }
        int m=(l+r)>>1;
        //根据L判断更新左还是右子树
        if(L<=m) 
            update(L,C,l,m,p<<1);
        else
            update(L,C,m+1,r,p<<1|1);
        PushUp(p);
    }

    区间更新

    说下add数组的意义。用来标记本节点已经更新过了,但是本节点的子节点并没有更新,所以需要进行下推操作

    下推操作:

    void PushDown(int ln,int rn,int p)//ln,rn表示左右子树的数字数量
    {
        if(add[p])
        {
            add[p<<1]+=add[p];
            add[p<<1|1]+=add[p];
            //修改子节点的sum使之与对应的add相对应
            sum[p<<1]+=add[p<<1]*ln;
            sum[p<<1|1]+=add[p<<1|1]*rn;
            //清除本节点标记 
            add[rt]=0;
        }
    }

    区间更新

    void update(int L,int R,int C,int l,int r,int p) //L,R为要更新的区间,C是值
    {
        if(L<=l&&r<=R) //如果当前区间完全在要更新的区间中
        {
            sum[p]+=C*(r-l+1);//更新数字和,向上保持正确
            add[p]+=C; //增加add标记,表示本区间的sum正确,子区间的sum仍然需要更新
            return;
        }
        int m=(l+r)>>1;
        PushDown(p,m-l+1,r-m); //下推标记
        //判断左右子树和[L,R]有无交集,有交集才能递归
        if(L<=m)
            update(L,R,C,l,m,p<<1);
        else
            update(L,R,C,m+1,r,p<<1|1);
        PushUp(p); //更新本节点
    }

    区间查询:

    int query(int L,int R,int l,int r,int p)
    {
        if(L<=l&&r<=R)
            return sum[p];
        int m=(l+r)>>1;
        //下推标记,否则sum可能不正确
        PushDown(p,m-l+1,r-m);
        
        int ans=0;
        if(L<=m)
            ans+=query(L,R,l,m,p<<1);
        else
            ans+=query(L,R,m+1,r,p<<1|1);
        return ans;
    }

    三 应用之扫描线

     用结构体存储矩形的x1,x2,y,flag(顶边或者底边),X数组存储所有的x下标值,Line记录所有的矩形定边和底边信息。将X和Line排序

    遍历Line,从最小的y值开始访问,找到对应x1,x2在X数组的下标值。用线段树维护矩形的变成,cover来代表点是否被覆盖(每出现一条顶边,要相应删除一条底边)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=10010;
    int sum[maxn<<2],cover[maxn<<2];//sum来维护某个区间的,cover表示是否覆盖 
    int X[maxn]; //存储所有的x下标值 
    struct Line
    {
        int lx,rx,y;
        int flag;
        Line(){}
        Line(int a,int b,int c,int d)
        {
            lx=a;rx=b;
            y=c;flag=d;
        }
        bool operator <(const Line&B)
        {
            return y<B.y;
        }
    
    }line[maxn];
    int find(int key,int n)
    {
        int low=1,high=n,mid;
        while(low<=high)
        {
            mid=(low+high)>>1;
            if(X[mid]==key)
                return mid;
            else if(X[mid]<key)
                low=mid+1;
            else
                high=mid-1;
        }
        return -1;
    }
    void PushUp(int u,int l,int r) //维护矩形的长 
    {
        if(cover[u])
        {
            sum[u]=X[r+1]-X[l];
        }
        else if(l==r)
            sum[u]=0;
        else
        {
            sum[u]=sum[u<<1]+sum[u<<1|1];
        }
    
    }
    void update(int p,int l,int r,int L,int R,int flag)
    {
        if(L<=l&&r<=R)
        {
            cover[p]+=flag;
            PushUp(p,l,r);
            return;
        }
        int m=(l+r)>>1; 
        if(L<=m) update(p<<1,l,m,L,R,flag);
        if(R>m) update(p<<l|1,m+1,r,L,R,flag);
        
        /*if(R<=m)
            update(p<<1,l,m,L,R,flag);
        else if(L>m)
            update(p<<1|1,m+1,r,L,R,flag);
        else
        {
            update(p<<1,l,m,L,m,flag);
            update(p<<1|1,m+1,r,m+1,R,flag);
        }
        PushUp(p,l,r);*/
    }
    int main()
    {
        int n,x1,y1,x2,y2;
        scanf("%d",&n);
        int num=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            X[++num]=x1;
            line[num]=Line(x1,x2,y1,1);
            X[++num]=x2;
            line[num]=Line(x1,x2,y2,-1);
        }
        sort(X+1,X+1+num);
        sort(line+1,line+1+num);
        int k=1;
        for(int i=2;i<=num;i++)
            if(X[i]!=X[i-1]) X[++k]=X[i];
        int ans=0;
        for(int i=1;i<=num;i++)
        {
            //找到扫描线左右端点在X数组中的下标
            int l=find(line[i].lx,k);
            int r=find(line[i].rx,k)-1; //为什么要-1
            if(l<=r)
                update(1,1,k,l,r,line[i].flag);
            for(int i=1;i<=20;i++)
                cout<<cover[i]<<" ";
            cout<<endl; 
            ans+=sum[1]*(line[i+1].y-line[i].y);
        }
        printf("%d
    ",ans);
    }
  • 相关阅读:
    [置顶] 一个懦弱的IT人
    Android ListView的理解(一)
    不允许调用库函数,也不允许使用任何全局或局部变量编写strlen函数
    http-使用get和post方式提交数据
    ILOG的一个基本应用——解决运输问题、转运问题
    原生js获取execl里面的值 主要使用ActiveXObject
    (顺序表的应用5.4.2)POJ 1591 M*A*S*H(约瑟夫环问题的变形——变换步长值)
    HDU 3032 Nim or not Nim? (sg函数)
    Hadoop入门实践之从WordCount程序说起
    仅复制备份(简单恢复模式)
  • 原文地址:https://www.cnblogs.com/flightless/p/8653657.html
Copyright © 2011-2022 走看看