线段树一般解决的问题是有关区间的动态修改和查询的问题。
首先要明确的是二叉查找树的性质,有点类似于二分查找的过程,可以仔细理解一下,下面说一下建立一棵树可以直接用数组来保存每个点,因为二叉树的性质,对于每个点来说,他的左孩子的编号是当前节点编号的二倍,表示为:rt<<2rt<<2,右孩子编号为其左孩子编号+1,表示为:rt<<1|1rt<<1|1
那么在更新操作的时候有两种写法,两种理解,第二种不是很好理解,但是写法简单,且不容易出错。下面介绍一下这两种写法:
但是注意无论是哪种写法,建图的时候都是将中间节点归到左孩子。
1.第一种理解是每次都将区间截取,考虑,如果所查询的区间刚好覆盖当前节点的话就将这个点的域(col[rt])直接更新;
2.取当前节点的中间点m=(l+r)>>1m=(l+r)>>1
3.如果要查询的区间的右端点RR满足R<=mR<=m 则只用更新左孩子,如果要查询的区间的左端点LL满足L>mL>m 则只用更新右孩子
4.如果上面都不满足,需要截取区间,更新左孩子,此时查找区间为LLmm,然后再跟新右孩子,此时的查找区间为m+1m+1R
代码:
Update(int L, int R, int c, int l, int r, int rt)//L,R是要查询的区间,l,r是当前点的区间
{
if(L==l&&R==r){
col[rt] = c;
return;
}
int m = (l+r)>>1;
if(R<=m) Update(L,R,c,l,m,rt<<1);
else if(L>m) Update(L,R,c,m+1,r,rt<<1|1);
else {
Update(L,m,c,l,m,rt<<1);
Update(m+1,R,c,m+1,r,rt<<1|1);
}
}
1.如果要询问的区间覆盖这个区间,即L<=l&&R>=rL<=l&&R>=r则对于这个点是一定要整个点更新的,所以直接更新并返回就可以了
2.取当前节点的中间点m=(l+r)>>1m=(l+r)>>1
3.如果当前要询问的点的右端点超过了中间节点的话即R>mR>m,说明右边的节点有需要更新的点,所以要将右孩子更新。
4.如果当前要询问的点的左端点超过了中间节点的话即L<=mL<=m,说明左边的节点有需要更新的点,所以要将左孩子更新。
这样代码比较简单,如下:
void Update(int L, int R, int l, int r, int c, int rt)
{
if( L <= l && R >= r )
{
col[rt] = c;
return;
}
if(col[rt]!=-1) PushDown(rt);
int m = (l+r)>>1;
if(L <= m ) Update(L,R,l,m,c,rt<<1);
if(R > m) Update(L,R,m+1,r,c,rt<<1|1);
}
离散化就是把原有的大二叉树压缩成小二叉树,但是压缩前后子区间关系不变。
例题:Mayor’s posters
The citizens of Bytetown, AB, could not stand that the candidates in the mayoral election campaign have been placing their electoral posters at all places at their whim. The city council has finally decided to build an electoral wall for placing the posters and introduce the following rules:
Every candidate can place exactly one poster on the wall.
All posters are of the same height equal to the height of the wall; the width of a poster can be any integer number of bytes (byte is the unit of length in Bytetown).
The wall is divided into segments and the width of each segment is one byte.
Each poster must completely cover a contiguous number of wall segments.
They have built a wall 10000000 bytes long (such that there is enough place for all candidates). When the electoral campaign was restarted, the candidates were placing their posters on the wall and their posters differed widely in width. Moreover, the candidates started placing their posters on wall segments already occupied by other posters. Everyone in Bytetown was curious whose posters will be visible (entirely or in part) on the last day before elections.
Your task is to find the number of visible posters when all the posters are placed given the information about posters’ size, their place and order of placement on the electoral wall.
Input
The first line of input contains a number c giving the number of cases that follow. The first line of data for a single case contains number 1 <= n <= 10000. The subsequent n lines describe the posters in the order in which they were placed. The i-th line among the n lines contains two integer numbers l i and ri which are the number of the wall segment occupied by the left end and the right end of the i-th poster, respectively. We know that for each 1 <= i <= n, 1 <= l i <= ri <= 10000000. After the i-th poster is placed, it entirely covers all wall segments numbered l i, l i+1 ,… , ri.
Output
For each input data set print the number of visible posters after all the posters are placed.
The picture below illustrates the case of the sample input.
Sample Input
1
5
1 4
2 6
8 10
3 4
7 10
Sample Output
4
代码①:
对于数字的离散化离散化可以想到,是将这些数字排序,对应的下标就是离散化结果,所以在找对应的下标的时候都要用到的是二分查找。但是这个题要注意一个问题,就是考虑110,14,6~10这三个大字报来说如果按照常规的离散化的思路来说的话,会出现问题,就是中间的空隙值没有离散化,为了避免这样的情况,我们每次讲这个点的右端点加1的值也加入这个数组,一起里散话,既可以避免这个问题了。
#include
#include
#include
#include
using namespace std;
const int N = 10005;
#define dd puts("haha");
int li[N],ri[N];
int x[N<<3];
int col[N<<4];
bool Hash[N];
void PushDown(int rt){
col[rt<<1] = col[rt];
col[rt<<1|1] = col[rt];
col[rt] = -1;
return;
}
void Update(int L, int R, int l, int r, int c, int rt)
{
if( L <= l && R >= r ) {
col[rt] = c;
return;
}
if(col[rt]!=-1) PushDown(rt);
int m = (l+r)>>1;
if(L <= m ) Update(L,R,l,m,c,rt<<1);
if(R > m) Update(L,R,m+1,r,c,rt<<1|1);
}
int ans;
void query(int l, int r, int rt)
{
if(l==r||~col[rt]){//~col[rt]相当于col[rt]!=-1;因为~是按位取反的意思
if(!Hash[col[rt]]&&(col[rt]!=-1)){
ans++;
Hash[col[rt]] = 1;
}
return;
}
if(~col[rt])PushDown(rt);
int m = (l+r)>>1;
query(l,m,rt<<1);
query(m+1,r,rt<<1|1);
}
int BSearch(int l, int r, int c)
{
int m;
while(l<=r){
m =(l+r)>>1;
if(x[m]==c) return m;
else if(x[m]<c) l = m+1;
else if(x[m]>c) r = m-1;
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(col,-1,sizeof(col));
memset(Hash,0,sizeof(Hash));
int n;
scanf("%d",&n);
int cnt = 1;
for(int i = 1; i <=n; i++)
{
scanf("%d %d",&li[i],&ri[i]);
x[cnt++] = li[i];
x[cnt++] = ri[i];
x[cnt++] = ri[i]+1;
}
sort(x+1,x+cnt+1);
int m = unique(x+1,x+cnt+1)-x-1;
for(int i = 1; i <= n; i++){
int ll = BSearch(1,m,li[i]);
int rr = BSearch(1,m,ri[i]);
//printf("(%d %d)\n",ll,rr);
Update(ll,rr,1,m,i,1);
}
//dd;
ans = 0;
query(1,m,1);
printf("%d\n",ans);
}
return 0;
}
代码②:
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;
int n,l[10005],r[10005];
int x[20005],vis[10005],a[80005];
void update(int k,int l,int r,int ll,int rr,int val)
{
if(l>=ll&&r<=rr)
{
a[k]=val;
return ;
}
if(a[k])
{
a[k<<1]=a[k<<1|1]=a[k];
a[k]=0;
}
int mid=(l+r)>>1;
if(ll<=mid) update(k<<1,l,mid,ll,rr,val);
if(rr>mid) update(k<<1|1,mid+1,r,ll,rr,val);
}
void query(int k,int l,int r)
{
if(a[k]) {vis[a[k]]=1;return ;}
if(l==r) return ;
int mid=(l+r)>>1;
query(k<<1,l,mid);
query(k<<1|1,mid+1,r);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int cnt=1,ans=0;
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&l[i],&r[i]);
x[cnt++]=l[i];
x[cnt++]=r[i];
}
sort(x+1,x+cnt);
int m=unique(x+1,x+cnt)-x-1;
memset(a,0,sizeof a);
for(int i=1;i<=n;i++)
{
int ll=lower_bound(x+1,x+m,l[i])-x;
int rr=lower_bound(x+1,x+m,r[i])-x;
update(1,1,m,ll,rr,i);
}
query(1,1,m);
for(int i=1;i<=n;i++)
if(vis[i]) ans++;
printf("%d\n",ans);
}
return 0;
}