鉴于去年西安赛区被吐槽为线段树专题赛区,就先更一发线段树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敌兵布阵,也可以做一下。