高级数据结构(1)、线段树与树状数组

鉴于去年西安赛区被吐槽为线段树专题赛区,就先更一发线段树2333333

线段树(Segment Tree)本质上来讲是一棵二叉搜索树。它与区间树类似,它的每一个结点都是一段区间。

线段树的功能是快速查找某个结点在若干线段中出现的次数,时间复杂度为O(logn),单纯空间复杂度为O(2n)。

实际应用中,为了避免越界一般来说开4n的数组。

树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。

主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值。

经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。

线段树和树状数组很类似,但树状数组能解决的问题一般线段树也能解决,而线段树能解决的树状数组就不一定了。

线段树至少包含以下几个操作:

1、Insert(x):插入元素

2、Delete(x):删除元素

3、Search(x):查找元素

但是只有这几个功能的话依旧没有什么用处。最简单的应用就是记录线段是否被覆盖,随时查询当前被覆盖线段的总长度。

那么此时可以在结点结构中加入一个变量int count;代表当前结点代表的子树中被覆盖的线段长度和。

这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根结点的count值了。

实际上,通过在结点上记录不同的数据,线段树还可以完成很多不同的任务。

例如,如果每次插入操作是在一条线段上每个位置均加k,而查询操作是计算一条线段上的总和,那么在结点上需要记录的值为sum。

线段树的使用有许多技巧,例如离散化、lazy标记、扫描线、动态开点、合并、可持久化等等。

本日例题:

Mayor's posters

  POJ - 2528 

题意:n张海报等高依次贴在墙上,给出每张海报的覆盖范围。后贴的会把先贴的覆盖掉,问最后能看到几张海报。

分析:本题很明显是一道线段树+离散化的题。线段树更新区间+染色。需要注意的是,给的长度可能非常大,有1e9.

如果裸开线段树的话很容易就会tle或者mle了。但是最多只有2e4个点,所以我们可以对区间离散化预处理。

TIP;什么是离散化?

离散化就是类似于将大区间映射到小区间的一种手段。例如:[1,5],[2,10],[3,10]就可以离散化为[1,3],[2,5],[3,5]。

但是有个问题,当两个相邻区间的间隔大于1的时候,离散化可能会出现问题。

例如:[1,10],[1,4],[5,10]

以及:[1,10],[1,4],[6,10]

离散化之后均为[1,4],[1,2],[3,4],可以看到对于第二个例子来说是不正确的,所以遇到这种情况我们需要手动插入一个结点。

此外本题还可以使用Lazy标记,即等到询问某个结点的时候再将之前的修改加入进去。

代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=11111;

int col[maxn<<4];
int li[maxn<<2],ri[maxn<<2];
bool Hash[maxn<<2];
int ans=0;
int x[maxn<<2];
int y[maxn<<2];

void Pushdown(int rt){//这儿是精髓,只有在查询或者update有交集的情况才会更新
    if(col[rt]!=-1){
        col[rt<<1]=col[rt<<1|1]=col[rt];
        col[rt]=-1;
    }
}

void update(int ql,int qr,int ch,int rt,int l,int r){
     if(ql<=l&&qr>=r)
    {
        col[rt]=ch;
        return;
    }
    Pushdown(rt);
    int m=(l+r)>>1;
    if(ql<=m)
        update(ql,qr,ch,lson);
    if(qr>m)
        update(ql,qr,ch,rson);
}

void query(int ql,int qr,int rt,int l,int r){
    if(col[rt]!=-1){
        if(!Hash[col[rt]]){//哈希表标记当前颜色是否被统计过
            ans++;
            Hash[col[rt]]=true;
        }
        col[rt]=-1;
        return ;
    }
    if(l==r) return;
 //   Pushdown(rt);
    int m=(l+r)>>1;
    if(ql<=m)
        query(ql,qr,lson);
    if(qr>=m)
        query(ql,qr,rson);
}

int bin(int ans,int x[],int l,int r){
    while(l<r){
        int m=(l+r)>>1;
        if(x[m]==ans)
            return m;
        if(ans<=x[m])
            r=m-1;
        else l=m+1;
    }
    return l;
}

int main(){
    int t;
    cin>>t;
    while(t--){
        ans=0;
        memset(col,-1,sizeof(col));
        memset(Hash,false,sizeof(Hash));
        int n;
        cin>>n;
        int cnt=1;
        for(int i=1;i<=n;i++){//离散化操作
            cin>>li[i]>>ri[i];
            x[cnt++]=li[i];
            x[cnt++]=ri[i];
        }
        sort(x+1,x+cnt);
        int mm=cnt;
        for(int i=2;i<cnt;i++){//判断是否加点
            if(x[i]-x[i-1]>1)
                x[mm++]=x[i-1]++;
        }
        sort(x+1,x+mm);
        int tt=2;
        y[1]=x[1];
        for(int i=2;i<mm;i++){
            if(x[i]!=x[i-1])
                y[tt++]=x[i];
        }
        for(int i=1;i<=n;i++){
            int l=bin(li[i],y,1,tt-1);
            int r=bin(ri[i],y,1,tt-1);
            update(l,r,i,1,1,tt-1);
        }
        query(1,tt-1,1,1,tt-1);
        cout<<ans<<endl;
    }


    return 0;
}

关于树状数组也有一道例题,hdu1166敌兵布阵,也可以做一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值