线段树全新版 【最近更新 9月8日】

本文详细介绍线段树的基本概念、实现技巧及其在不同场景的应用案例,包括单点更新、成段更新、区间合并等核心功能,并提供了丰富的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

修改自:http://www.notonlysuccess.com/

在代码前先介绍一些我的线段树风格:

 

N是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于N的最小2x的两倍

lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示

以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便

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

PushDown(int rt)是把当前结点的信息更新给儿子结点

rt表示当前子树的根(root),也就是当前所在的结点

整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:


1.单点更新:

最最基础的线段树,只更新叶子节点,然后把信息用PushUp(int r)这个函数更新上来

 

hdu1166 敌兵布阵

题意:O(-1)

思路:O(-1)

线段树功能:update:单点增减 query:区间求和

//单点增减 区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;

const int N=55555;
int sum[N<<2];//四倍

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

void build(int l,int r,int rt){
  if(l==r){
    scanf("%d",&sum[rt]);
    return ;
  }
  int m=(l+r)>>1;
  build(lson);
  build(rson);
  PushUp(rt);
}

void update(int p,int add,int l,int r,int rt){
  if(l==r){
    sum[rt]+=add;
    return ;
  }
  int m=(l+r)>>1;
  if(p<=m)update(p, add, lson);
  else update(p, add, rson);
  PushUp(rt);
}

int query(int L,int R,int l,int r,int rt){
  if(L<=l && r<=R){
    return sum[rt];
  }
  int m=(l+r)>>1;
  int ret=0;
  if(L<=m)ret+=query(L, R, lson);
  if(R>m)ret+=query(L, R, rson);
  return ret;
}

int main(){
  int T,n;
  scanf("%d",&T);
  for(int cas=1; cas<=T; cas++){
    printf("Case %d:\n",cas);
    scanf("%d",&n);
    build(1, n, 1);
    char op[10];
    while(scanf("%s",op)){
      if(op[0]=='E')break;
      int a,b;
      scanf("%d%d",&a,&b);
      if(op[0]=='Q')printf("%d\n",query(a, b, 1, n, 1));
      else if(op[0]=='S')update(a, -b, 1, n , 1);
      else update(a, b, 1, n, 1);
    }
  }
  return 0;
}


hdu1754 I Hate It

题意:O(-1)

思路:O(-1)

线段树功能:update:单点替换 query:区间最值

//单点替换 区间最值
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;

const int N=222222;
int Max[N<<2];

void PushUp(int rt){
  Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);
}

void build(int l,int r,int rt){
  if(l==r){
    scanf("%d",&Max[rt]);
    return ;
  }
  int m=(l+r)>>1;
  build(lson);
  build(rson);
  PushUp(rt);
}

void update(int p,int re,int l,int r,int rt){
  if(l==r){
    Max[rt]=re;
    return ;
  }
  int m=(l+r)>>1;
  if(p<=m)update(p, re, lson);
  else update(p, re, rson);
  PushUp(rt);
}

int query(int L,int R,int l,int r,int rt){
  if(L<=l && r<=R){
    return Max[rt];
  }
  int m=(l+r)>>1;
  int ret=0;
  if(L<=m)ret=max(ret, query(L, R, lson));
  if(R>m)ret=max(ret,query(L, R, rson));
  return ret;
}

int main(){
  int n,m;
  while(~scanf("%d%d",&n,&m)){
    build(1, n, 1);
    int a,b;
    char op[2];
    while(m--){
      scanf("%s%d%d",op,&a,&b);
      if(op[0]=='Q')printf("%d\n",query(a, b, 1, n, 1));
      else update(a, b, 1, n, 1);
    }
  }
  return 0;
}

hdu1394 Minimum Inversion Number

题意:求Inversion后的最小逆序数个数

思路:

先百科一下逆序数的概念: 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个 逆序 。一个排列中逆序的总数就称为这个排列的 逆序数 。逆序数为 偶数 的排列称为 偶排列 ;逆序数为奇数的排列称为 奇排列 。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。

逆序数计算方法是:在逐个元素读取原始数列时,每次读取都要查询当前已读取元素中大于当前元素的个数,加到sum里,最后得到的sum即为原始数列的逆序数。

接下来找数列平移后得到的最小逆序数,假设当前序列逆序数是sum,那么将a[0]移到尾部后逆序数的改变是之前比a[0]大的数全部与尾部a[0]组合成逆序数,假设数量为x,则x=n-1-a[0],而之前比a[0]小的数(也就是之前能和a[0]组合为逆序数的元素)不再与a[0]组合成逆序数,假设数量为y,则y=n-x-1,这样,新序列的逆序数就是sum+x-y=sum-2*a[0]+n-1;

接下来说明下线段树的作用,线段区间表示当前已读取的元素个数,比如[m,n]表示在数字m到n之间有多少个数已经读入,build时所有树节点全部为0就是因为尚未读数,update函数是将新读入的数字更新到线段树里,点更新,query函数是查询当前数字区间已存在的数字个数。

用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解

线段树功能:update:单点增减 query:区间求和

//关键在于如何用线段树维护
//方法:暴力或者线段树或者归并排序
//单点替换  区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;

const int N=5555;
int sum[N<<2];
int a[N];

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

void build(int l,int r,int rt){
  sum[rt]=0;
  if(l==r)return ;
  int m=(l+r)>>1;
  build(lson);
  build(rson);
}

void update(int p,int l,int r,int rt){
  if(l==r){
    sum[rt]=1;
    return ;
  }
  int m=(l+r)>>1;
  if(p<=m)update(p, lson);
  else update(p, rson);
  PushUp(rt);
}

//询问区间[L, R]中目前的数字个数
int query(int L,int R,int l,int r,int rt){
  if(L<=l && r<=R){
    return sum[rt];
  }
  int m=(l+r)>>1;
  int ret=0;
  if(L<=m)ret+=query(L, R, lson);
  if(R>m)ret+=query(L, R, rson);
  return ret;
}

int main(){
  int n;
  while(~scanf("%d",&n)){
    build(0, n-1, 1);
    int sum=0;//求原始数列的逆序数
    for(int i=0; i<n; i++){
      scanf("%d",&a[i]);
      //查询当前比a[i]大的数有多少个
      sum+=query(a[i], n-1, 0, n-1, 1);
      update(a[i], 0, n-1, 1);
    }
    int ans=sum;
    for(int i=0; i<n; i++){
      sum+=n- 2*a[i] -1;
      ans=min(ans, sum);
    }
    printf("%d\n",ans);
  }
  return 0;
}


hdu2795 Billboard

题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子

思路:每次找到最大值的位子,然后减去L

线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)

//单点增减 区间最值
/*************************************************
题目大意:
有一块h*w的矩形广告板,要往上面贴广告;
然后给n个1*wi的广告,要求把广告贴上去;
而且要求广告要尽量往上贴并且尽量靠左;
求第n个广告的所在的位置,不能贴则为-1;

算法思想:
利用线段树可以求区间的最大值;
将位置即h用来建树(h<=n,大了没有意义);
树中存储的为该位置还拥有的空间;
若左子树的最大值大于他,就查询左子树,否则查询右子树;
**************************************************/
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;

const int N=222222;
int Max[N<<2];
int h,w,n;

void PushUp(int rt){
  Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);
}

void build(int l,int r,int rt){
  Max[rt]=w;
  if(l==r)return ;
  int m=(l+r)>>1;
  build(lson);
  build(rson);
}

int query(int x,int l,int r,int rt){
  if(l==r){
    Max[rt]-=x;
    return l;
  }
  int m=(l+r)>>1;
  int ret=0;
  ret=(Max[rt<<1]>=x) ? query(x, lson):query(x,rson);
  PushUp(rt);
  return ret;
}

int main(){
  while(~scanf("%d%d%d",&h,&w,&n)){
    if(h>n)h=n;
    build(1, h, 1);
    int x;
    while(n--){
      scanf("%d",&x);
      if(Max[1]<x)puts("-1");
      else printf("%d\n",query(x, 1, h, 1));
    }
  }
  return 0;
}
练习:

poj2828 Buy Tickets

poj2886 Who Gets the Most Candies?


2.成段更新

(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候

 

hdu1698 Just a Hook

题意:O(-1)

思路:O(-1)

线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)

//用延迟标记使得更新延迟到  *下次   *需要  更新or询问到的时候
//成段替换 区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;

const int N=111111;
int sum[N<<2];
int col[N<<2];

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

void PushDown(int rt,int m){
  if(col[rt]){
    col[rt<<1]=col[rt<<1|1]=col[rt];
    sum[rt<<1]=(m- (m>>1)) * col[rt];
    sum[rt<<1|1]=(m>>1) * col[rt];
    col[rt]=0;
  }
}

void build(int l,int r,int rt){
  col[rt]=0;
  sum[rt]=1;
  if(l==r)return ;
  int m=(l+r)>>1;
  build(lson);
  build(rson);
  PushUp(rt);
}

void update(int L,int R,int c,int l,int r,int rt){
  if(L<=l && r<=R){
    col[rt]=c;
    sum[rt]=c*(r-l+1);
    return ;
  }
  PushDown(rt, r-l+1);
  int m=(l+r)>>1;
  if(L<=m)update(L, R, c, lson);
  if(R>m) update(L, R, c, rson);
  PushUp(rt);
}

int main(){
  int T,n,m;
  scanf("%d",&T);
  for(int cas=1; cas<=T; cas++){
    scanf("%d%d",&n,&m);
    build(1, n, 1);
    while(m--){
      int a,b,c;
      scanf("%d%d%d",&a,&b,&c);
      update(a, b, c, 1, n, 1);
    }
    printf("Case %d: The total value of the hook is %d.\n",cas,sum[1]);
  }
  return 0;
}


poj3468 A Simple Problem with Integers

题意:O(-1)

思路:O(-1)

线段树功能:update:成段增减 query:区间求和

//成段增减 区间求和
//G++2594ms C++1735ms
#include <cstdio>
#include <algorithm>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
#define ll long long
using namespace std;

const int N=111111;
ll sum[N<<2];
ll add[N<<2];

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

void PushDown(int rt,int m){
  if(add[rt]){
    add[rt<<1]+=add[rt];
    add[rt<<1|1]+=add[rt];
    sum[rt<<1]+=(m- (m>>1)) * add[rt];
    sum[rt<<1|1]+=(m>>1) * add[rt];
    add[rt]=0;
  }
}

void build(int l,int r,int rt){
  add[rt]=0;
  if(l==r){
    scanf("%lld",&sum[rt]);
    return ;
  }
  int m=(l+r)>>1;
  build(lson);
  build(rson);
  PushUp(rt);
}

void update(int L,int R,int c,int l,int r,int rt){
  if(L<=l && r<=R){
    add[rt]+=c;
    sum[rt]+=(ll) c*(r-l+1);
    return ;
  }
  PushDown(rt, r-l+1);
  int m=(l+r)>>1;
  if(L<=m)update(L, R, c, lson);
  if(R>m) update(L, R, c, rson);
  PushUp(rt);
}

ll query(int L,int R,int l,int r,int rt){
  if(L<=l && r<=R){
    return sum[rt];
  }
  PushDown(rt, r-l+1);
  int m=(l+r)>>1;
  ll ret=0;
  if(L<=m)ret+=query(L, R, lson);
  if(R>m)ret+=query(L, R, rson);
  return ret;
}

int main(){
  int N,Q;
  scanf("%d%d",&N,&Q);
  build(1, N, 1);
  while(Q--){
    char op[2];
    int a,b,c;
    scanf("%s",op);
    if(op[0]=='Q'){
      scanf("%d%d",&a,&b);
      printf("%lld\n",query(a, b, 1, N, 1));
    }else{
      scanf("%d%d%d",&a,&b,&c);
      update(a, b, c, 1, N, 1);
    }
  }
  return 0;
}



poj2528 Mayor’s posters

题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报

思路:这题数据范围很大,直接搞超时+超内存,需要离散化:

离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了

所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多

而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(poj这题数据奇弱)

如三张海报为:1~10 1~4 6~10

离散化时 X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 6, X[ 4 ] = 10
第一张海报时:墙的1~4被染为1;
第二张海报时:墙的1~2被染为2,3~4仍为1;
第三张海报时:墙的3~4被染为3,1~2仍为2。
最终,第一张海报就显示被完全覆盖了,于是输出2,但实际上明显不是这样,正确输出为3。

为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,4,6,10]

如果相邻数字间距大于1的话,在其中加上任意一个数字,例如在上面1 4 6 10中间加5(算法中实际上1,4之间,6,10之间都新增了数的)

X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 5, X[ 4 ] = 6, X[ 5 ] = 10

这样之后,第一次是1~5被染成1;第二次1~2被染成2;第三次4~5被染成3

最终,1~2为2,3为1,4~5为3,于是输出正确结果3。

线段树功能:update:成段替换 query:简单hash

//C++ 78ms  G++ RTE
#include<cstdio>
#include<algorithm>
#include<cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using  namespace  std;
const int N=11111;
bool hash[N];
int li[N],ri[N];
int x[N*3];
int col[N<<4];
int cnt;

void PushDown(int rt){
  if(col[rt]!=-1){
    col[rt<<1]=col[rt<<1|1]=col[rt];
    col[rt]=-1;
  }
}

void update(int L,int R,int c,int l,int r,int rt){
  if(L<=l && r<=R){
    col[rt]=c;
    return ;
  }
  PushDown(rt);
  int m=(l+r)>>1;
  if(L<=m)update(L, R, c, lson);
  if(R>m)update(L, R, c, rson);
}

void query(int l,int r,int rt){
  if(col[rt]!=-1){
    if(!hash[col[rt]])cnt++;
    hash[ col[rt] ]=true;
    return ;
  }
  if(l==r)return ;
  int m=(l+r)>>1;
  query(lson);
  query(rson);
}

int bin(int key,int n,int x[]){//二分查找
  int l=0,r=n-1;
  while(l<=r){
    int m=(l+r)>>1;
    if(x[m]==key)return m;
    if(x[m]<key)l=m+1;
    else r=m-1;
  }
  return -1;
}

int  main(){
  int T,n;
  scanf("%d",&T);
  while(T--){
    scanf("%d",&n);
    int nn=0;
    for(int i=0; i<n; i++){
      scanf("%d%d",&li[i], &ri[i]);
      x[nn++]=li[i];
      x[nn++]=ri[i];
    }
    sort(x,x+nn);
    int m=1;
    for(int i=1; i<nn; i++)
      if(x[i]!=x[i-1])x[m++]=x[i];
    for(int i=m-1; i>0; i--)
      if(x[i]!=x[i-1]+1)x[m++]=x[i-1]+1;
    sort(x,x+m);
    Memset(col, -1);
    for(int i=0; i<n; i++){
      int l=bin(li[i], m, x);
      int r=bin(ri[i], m, x);
      update(l, r, i, 0, m-1, 1);
    }
    cnt=0;
    Memset(hash, false);
    query(0, m-1, 1);
    printf("%d\n",cnt);
  }
  return 0;
}



poj3225 Help with Intervals

题意:区间操作,交,并,补等

思路:

我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)

  • U T S ← S ∪ T 即将[l,r]标记为1  
  • I T S ← S ∩ T 即将-oo~l和r~+oo标记为0,因为是并集,所以并集后的集合s一定在[l,r]内,则在l,r内的集合被标记是什么状态就是什么状态(表示是否属于s),[l,r]外的集合不属于s所以标记为0  
  • D T S ← S - T  即将[l,r]标记为0,则在[l,r]内被s包含的集合也会标记为0表示不再属于s  
  • C T S ← T - S  即先将-oo~l,r~+oo标记为0,这部分不属于[l,r]则一定不属于s,然后将[l,r]的标记0/1互换,因为属于s的不再属于s,不属于s的将属于s  





  • S T S ← S ⊕ T  即属于s的不变,[l,r]中不属于s的(区间)0标记为1,属于s的(区间)1标记为0,所以[l,r]的标记0/1互换 

    成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作

    很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了

    所以当一个节点得到覆盖标记时把异或标记清空

    而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记

     

    开区间闭区间只要数字乘以2就可以处理(偶数表示包括端点,奇数表示不包括)

    线段树功能:update:成段替换,区间异或 query:简单mark

    #include <cstdio>
    #include <cstring>
    #include <cctype>
    #include <algorithm>
    #define lson l, m, rt<<1
    #define rson m+1, r, rt<<1|1
    using namespace std;
    
    const int maxn=65535*2+2;
    int cover[maxn<<2],Xor[maxn<<2];//cover表示区间的0,1状态,Xor表示该区间转换的状态
    bool mark[maxn];
    
    void UpdateXor(int rt){
      if(cover[rt]!=-1)cover[rt]^=1;//表示整个区间可以同时转换
      else Xor[rt]^=1;
    }
    
    void PushDown(int rt){
      if(cover[rt]!=-1){//表示该节点标记了但是孩子未标记
        cover[rt<<1]=cover[rt<<1|1]=cover[rt];
        Xor[rt<<1]=Xor[rt<<1|1]=0;//区间全被覆盖则原来的转换无任何作用了
        cover[rt]=-1;
      }
      if(Xor[rt]){//表示该点转换了但是孩子未转换
        UpdateXor(rt<<1);
        UpdateXor(rt<<1|1);
        Xor[rt]=0;
      }
    }
    
    void update(char op,int L,int R,int l,int r,int rt){
      if(L<=l && r<=R){
        if(op=='U'){
          cover[rt]=1;
          Xor[rt]=0;
        }else if(op=='D'){
          cover[rt]=0;
          Xor[rt]=0;
        }else if(op=='C'||op=='S'){
          UpdateXor(rt);
        }
        return ;
      }
      PushDown(rt);//是否该节点标记或转换过而孩子未标记或转换
      int m=(l+r)>>1;
      if(L<=m)update(op, L, R, lson);
      else if(op=='I'||op=='C'){
        Xor[rt<<1]=cover[rt<<1]=0;//将-oo~l中的-oo~mid标记为0
      }
      if(m<R)update(op, L, R, rson);
      else if(op=='I'||op=='C'){
        Xor[rt<<1|1]=cover[rt<<1|1]=0;//将r~+oo中的mid+1~+oo标记为0
      }
    }
    
    void query(int l,int r,int rt){
      if(cover[rt]==1){
        for(int i=l; i<=r; i++){
          mark[i]=true;
        }
        return ;
      }else if(cover[rt]==0)return ;
      if(l==r)return ;
      PushDown(rt);
      int m=(l+r)>>1;
      query(lson);
      query(rson);
    }
    
    int main(){
      #ifndef ONLINE_JUDGE
      freopen("in.txt","r",stdin);
      #endif
    
      cover[1]=Xor[1]=0;
      char op,l,r;
      int a,b;
      while(~scanf("%c %c%d,%d%c\n",&op, &l, &a, &b, &r)){
        a<<=1;b<<=1;
        if(l=='(')a++;
        if(r==')')b--;
        if(a>b){//比如输入区间为I (1,1)或C (1,1)
          if(op=='C' || op=='I'){//将-oo~l,r~+oo标记为0
            cover[1]=Xor[1]=0;
          }
        }else update(op, a, b, 0, maxn, 1);
      }
      query(0, maxn, 1);
      bool flag=false;
      int s=-1,t;
      for(int i=0; i<=maxn; i++){
        if(mark[i]){
          if(s==-1)s=i;
          t=i;
        }else{
          if(s!=-1){
            if(flag)printf(" ");  //比如[1,3) (3,5]
            flag=true;
            printf("%c%d,%d%c",s&1?'(':'[', s>>1, (t+1)>>1, t&1?')':']');
            s=-1;
          }
        }
      }
      if(!flag)printf("empty set");
      puts("");
      return 0;
    }


    练习:

    poj1436 Horizontally Visible Segments

    poj2991 Crane

    Another LCIS

    Bracket Sequence




    3.区间合并

    这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并

     

    poj3667 Hotel

    题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边

    2 a b:将[a,a+b-1]的房间清空

    思路:记录区间中最长的空房间

    线段树操作:update:区间替换 query:询问满足条件的最左断点

    #include <cstdio>
     
    #include <cstring>
     
    #include <cctype>
     
    #include <algorithm>
     
    using namespace std;
     
    #define lson l , m , rt << 1
     
    #define rson m + 1 , r , rt << 1 | 1
     
      
     
    const int maxn = 55555;
     
    int lsum[maxn<<2] , rsum[maxn<<2] , msum[maxn<<2];
     
    int cover[maxn<<2];
     
      
     
    void PushDown(int rt,int m) {
     
           if (cover[rt] != -1) {
     
                  cover[rt<<1] = cover[rt<<1|1] = cover[rt];
     
                  msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = cover[rt] ? 0 : m - (m >> 1);
     
                  msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = cover[rt] ? 0 : (m >> 1);
     
                  cover[rt] = -1;
     
           }
     
    }
     
    void PushUp(int rt,int m) {
     
           lsum[rt] = lsum[rt<<1];
     
           rsum[rt] = rsum[rt<<1|1];
     
           if (lsum[rt] == m - (m >> 1)) lsum[rt] += lsum[rt<<1|1];
     
           if (rsum[rt] == (m >> 1)) rsum[rt] += rsum[rt<<1];
     
           msum[rt] = max(lsum[rt<<1|1] + rsum[rt<<1] , max(msum[rt<<1] , msum[rt<<1|1]));
     
    }
     
    void build(int l,int r,int rt) {
     
           msum[rt] = lsum[rt] = rsum[rt] = r - l + 1;
     
           cover[rt] = -1;
     
           if (l == r) return ;
     
           int m = (l + r) >> 1;
     
           build(lson);
     
           build(rson);
     
    }
     
    void update(int L,int R,int c,int l,int r,int rt) {
     
           if (L <= l && r <= R) {
     
                  msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1;
     
                  cover[rt] = c;
     
                  return ;
     
           }
     
           PushDown(rt , r - l + 1);
     
           int m = (l + r) >> 1;
     
           if (L <= m) update(L , R , c , lson);
     
           if (m < R) update(L , R , c , rson);
     
           PushUp(rt , r - l + 1);
     
    }
     
    int query(int w,int l,int r,int rt) {
     
           if (l == r) return l;
     
           PushDown(rt , r - l + 1);
     
           int m = (l + r) >> 1;
     
           if (msum[rt<<1] >= w) return query(w , lson);
     
           else if (rsum[rt<<1] + lsum[rt<<1|1] >= w) return m - rsum[rt<<1] + 1;
     
           return query(w , rson);
     
    }
     
    int main() {
     
           int n , m;
     
           scanf("%d%d",&n,&m);
     
           build(1 , n , 1);
     
           while (m --) {
     
                  int op , a , b;
     
                  scanf("%d",&op);
     
                  if (op == 1) {
     
                         scanf("%d",&a);
     
                         if (msum[1] < a) puts("0");
     
                         else {
     
                                int p = query(a , 1 , n , 1);
     
                                printf("%d\n",p);
     
                                update(p , p + a - 1 , 1 , 1 , n , 1);
     
                         }
     
                  } else {
     
                         scanf("%d%d",&a,&b);
     
                         update(a , a + b - 1 , 0 , 1 , n , 1);
     
                  }
     
           }
     
           return 0;
     
    }

    练习:

    hdu3308 LCIS

    hdu3397 Sequence operation

    hdu2871 Memory Control

    hdu1540 Tunnel Warfare  代码:点击打开链接

    CF46-D Parking Lot

    扫描线

    这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去

    最典型的就是矩形面积并,周长并等题

     

    hdu1542 Atlantis

    题意:矩形面积并     具体可参考:点击打开链接

    思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个

    线段树操作:update:区间增减 query:直接取根节点的值   代码:点击打开链接      

    #include <cstdio>
    #include <cstring>
    #include <cctype>
    #include <algorithm>
    using namespace std;
    #define lson l , m , rt << 1
    #define rson m + 1 , r , rt << 1 | 1
    const int maxn = 2222;
    
    
    int cnt[maxn << 2];
    double sum[maxn << 2];
    double X[maxn];
    struct Seg {
        double h , l , r;//扫描线的左右端点x坐标,扫描线的高度
        int s; //为1或-1,标记扫描线是矩形的上位还是下位边.
        Seg(){}
        Seg(double a,double b,double c,int d) : l(a) , r(b) , h(c) , s(d) {}
        bool operator < (const Seg& rhs) const {
            return h < rhs.h;
        }
    }ss[maxn];
    
    
    void PushUp(int rt,int l,int r) {
        if (cnt[rt]) sum[rt] = X[r+1] - X[l];
        else if (l == r) sum[rt] = 0;
        else sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    
    void update(int L,int R,int c,int l,int r,int rt) { //区间增减
        if (L <= l && r <= R) {
            cnt[rt] += c;
            PushUp(rt , l , r);
            return ;
        }
        int m = (l + r) >> 1;
        if (L <= m) update(L , R , c , lson);
        if (m < R) update(L , R , c , rson);
        PushUp(rt , l , r);
    }
    
    
    int main() {
        int n , cas = 1;
        while (~scanf("%d",&n) && n) {
            int m = 0;
            while (n --) {
                double a , b , c , d;
                scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
                X[m] = a;
                ss[m++] = Seg(a , c , b , 1);
                X[m] = c;
                ss[m++] = Seg(a , c , d , -1);
            }
            sort(X , X + m); //X按从小到大排序
            sort(ss , ss + m);//按h值从小到大排序
            int k = 1; //对X去重,并且用k表示一共有多少个X
            for (int i = 1 ; i < m ; i ++) {
                if (X[i] != X[i-1]) X[k++] = X[i];
            }//当我们需要找到第i个区域的两端点坐标时,只需要X[i]和X[i+1]
            memset(cnt , 0 , sizeof(cnt));//cnt: >=0时表示本节点控制的区域内下位边个数-上位边个数的结果.
            //如果==-1时,表示本节点左右子树的上下位边数不一致.
            memset(sum , 0 , sizeof(sum)); //sum: 本节点控制的区域内cnt值不为0的区域总长度.
            double ret = 0;
            for (int i = 0 ; i < m - 1 ; i ++) {
                int l = lower_bound(X, X+k, ss[i].l)-X;
                int r = lower_bound(X, X+k, ss[i].r)-X-1;
                if (l <= r) update(l , r , ss[i].s , 0 , k - 1, 1);//线段树从0~k-1,也就是X的下标
                ret += sum[1] * (ss[i+1].h - ss[i].h);
            }
            printf("Test case #%d\n", cas++);
            printf("Total explored area: %.2f\n\n", ret);
        }
        return 0;
    }
    
  • hdu1828 Picture

    题意:矩形周长并

    思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)

    线段树操作:update:区间增减 query:直接取根节点的值

    #include <cstdio>
     
    #include <cstring>
     
    #include <cctype>
     
    #include <algorithm>
     
    using namespace std;
     
    #define lson l , m , rt << 1
     
    #define rson m + 1 , r , rt << 1 | 1
     
      
     
    const int maxn = 22222;
     
    struct Seg{
     
           int l , r , h , s;
     
           Seg() {}
     
           Seg(int a,int b,int c,int d):l(a) , r(b) , h(c) , s(d) {}
     
           bool operator < (const Seg &cmp) const {
     
                  return h < cmp.h;
     
           }
     
    }ss[maxn];
     
    bool lbd[maxn<<2] , rbd[maxn<<2];
     
    int numseg[maxn<<2];
     
    int cnt[maxn<<2];
     
    int len[maxn<<2];
     
    void PushUP(int rt,int l,int r) {
     
           if (cnt[rt]) {
     
                  lbd[rt] = rbd[rt] = 1;
     
                  len[rt] = r - l + 1;
     
                  numseg[rt] = 2;
     
           } else if (l == r) {
     
                  len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0;
     
           } else {
     
                  lbd[rt] = lbd[rt<<1];
     
                  rbd[rt] = rbd[rt<<1|1];
     
                  len[rt] = len[rt<<1] + len[rt<<1|1];
     
                  numseg[rt] = numseg[rt<<1] + numseg[rt<<1|1];
     
                  if (lbd[rt<<1|1] && rbd[rt<<1]) numseg[rt] -= 2;//两条线重合
     
           }
     
    }
     
    void update(int L,int R,int c,int l,int r,int rt) {
     
           if (L <= l && r <= R) {
     
                  cnt[rt] += c;
     
                  PushUP(rt , l , r);
     
                  return ;
     
           }
     
           int m = (l + r) >> 1;
     
           if (L <= m) update(L , R , c , lson);
     
           if (m < R) update(L , R , c , rson);
     
           PushUP(rt , l , r);
     
    }
     
    int main() {
     
           int n;
     
           while (~scanf("%d",&n)) {
     
                  int m = 0;
     
                  int lbd = 10000, rbd = -10000;
     
                  for (int i = 0 ; i < n ; i ++) {
     
                         int a , b , c , d;
     
                         scanf("%d%d%d%d",&a,&b,&c,&d);
     
                         lbd = min(lbd , a);
     
                         rbd = max(rbd , c);
     
                         ss[m++] = Seg(a , c , b , 1);
     
                         ss[m++] = Seg(a , c , d , -1);
     
                  }
     
                  sort(ss , ss + m);
     
                  int ret = 0 , last = 0;
     
                  for (int i = 0 ; i < m ; i ++) {
     
                         if (ss[i].l < ss[i].r) update(ss[i].l , ss[i].r - 1 , ss[i].s , lbd , rbd - 1 , 1);
     
                         ret += numseg[1] * (ss[i+1].h - ss[i].h);
     
                         ret += abs(len[1] - last);
     
                         last = len[1];
     
                  }
     
                  printf("%d\n",ret);
     
           }
     
           return 0;
     
    }

    练习:

     

    hdu3265 Posters

    hdu3642 Get The Treasury

    poj2482 Stars in Your Window

    poj2464 Brownie Points II

    hdu3255 Farming

    ural1707 Hypnotoad’s Secret

    uva11983 Weird Advertisement

     

     

    线段树与其他结合练习

     (欢迎大家补充):

    hdu3333 Turing Tree

    hdu3874 Necklace

    hdu3016 Man Down

    hdu3340 Rain in ACStar

    zju3511 Cake Robbery

    UESTC1558 Charitable Exchange

    CF85-D Sum of Medians

    spojGSS2 Can you answer these queries II

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值