【线段树(递归版)】—— 点修改 区间覆盖 区间修改

线段树接触了也很久,但总是没一个安定的写法,各种花里呼哨的都写过,老实的时候连指针版的都写过。
但款式那么多并没有用,到时候用起来反而坑坑洼洼的,还不如定一个标准模式。


虽然是非递归的线段树代码简单而高效,但是万事慢慢来,从最基础简单的在博客开始写起。
此次我们介绍的是递归版的线段树。


“线段树是一种特殊的区间树“


线段树是什么东西?

简单的说,线段树是把 [1,n] 一段区间不断二分二分,直至每个树的节点是[i,i]的区间

                                               [1,7]

                            [1,4]                             [5,7]

                [1,2]          [3,4]                   [5,6]            [7,7]

        [1,1]    [2,2]        [3,3]  [4,4]      [5,5] [6,6]     

(线段树未必是一个满二叉树,但一定是个平衡二叉树)


/* 讨论在数组的情况下构造*/

Section A 基本操作

一、如何构造?

a)      如何构造左右子树关系?
假设根节点为root,左儿子lson 右儿子rson

不妨lson = root << 1 , rson = lson | 1
画一张图,把节点序号的二进制写出来你就知道为什么这么做

b)      如何建立树?
从最大的区间开始,每次二分 mid = (lson+rson) >> 1
递归建立lson,mid区间和mid+1,rson区间
当区间只有一个数的时候,保存数据

c)      更新:
在构造过程中首先是build(lson,mid),build(mid+1,rson)
然后需要发挥线段树的功能,能够快速询问维护一段区间的值比如

tree[root].max = max{tree[lson].max,tree[rson],max}

tree[root].sum = tree[lson].sum + tree[rson].sum

 

二、询问操作

a)      朴素法
在构造时,对于每个节点保存左右区间的范围。
然后递归寻找Query(root,L,R)
分三类情况:
完全在左区间 Query(lson,L,R)
完全在右区间 Query(rson,L,R)
区间跨越范围 Query(lson,L,Mid),Query(rson,Mid+1,r)

b)      全局法:
在构造时:不需要考虑每个节点的范围
根据Query函数调用时的区间和查询所用的区间直接判断。

设L,R是当前调用询问的区间;qL,qR是查询区间

还是分二类情况:若[L,R] 包含于 [qL,qR] 直接考虑当前root的值

否则考虑qR,qL与Mid的关系
若qL <= Mid 考虑Query(lson,qL,Mid)
若Mid < qR 则考虑Query(rson,Mid+1,qR)

        
至于最大最小还是求和怎么操作,应该心里有数

事实上,给定了线段长度,每个节点本身就对应了一段区间,朴素法其实有些多余,不建议使用。

 

三、单点修改 /* 以下均用全局法*/

       a)      单点修改

            其实和单点查询的思路一摸一样,找到后顺便更新就是。

       b)单点覆盖

       把a中的 += 变成 =


/* 有人可能会问为什么这里没有build函数,那是因为所谓build,也就是在每个节点上add一次罢了 */

#include  
using namespace std;
typedef long long long_int; 
const int INF = 1000000000;
const int maxn = 500100;
const int maxnode = maxn << 2; 

bool isSet = true;
int op, qL, qR, pos, val;  //pos表示修改的位置 val表示修改量 
long_int _sum , _min , _max;


struct IntervalTree {
  long_int minv[maxnode] , maxv[maxnode] , sumv[maxnode];
  inline void init(int x , long_int val){
  	  minv[x] = maxv[x] = sumv[x] = val;  //把某个单节点区域初始化,最大最小和都是这个值 
  }
  inline void add(int x , long_int val){
  	  
  	  /* 在下标为x的树节点上 所有维护的值都加上val 即单点增加*/ 
  	  minv[x] += val;
  	  maxv[x] += val;
  	  sumv[x] += val;
  }
  inline void pushup(int o){ 
    /* 更新下标为 o 的树节点*/
  	int lson = o << 1;
  	int rson = lson | 1;
  	minv[o] = min(minv[lson],minv[rson]);
  	sumv[o] = sumv[lson] + sumv[rson];
  	maxv[o] = max(maxv[lson],maxv[rson]);
  }

  void update(int o, int L, int R) {
    int M = (L + R) >> 1;
    if(L == R) {
    	if(isSet) init(o,val);
    	else add(o,val);
	}
    else {     
      if(pos <= M) update(o*2, L, M); 
	  else update(o*2+1, M+1, R);
      pushup(o);
    }
  }

  void query(int o, int L, int R) {
    int M = (L+R) >> 1;
    if(qL <= L && R <= qR)
    {
    	_min = min(_min,minv[o]);
    	_max = max(_max,maxv[o]);
    	_sum += sumv[o];
    	return;
	}
    if(qL <= M) 
    	query(o*2, L, M); 
    if(M < qR)
		query(o*2+1, M+1, R);  
  }
};


IntervalTree tree;

int main() {
  int n, m;
	cin >> n;
    memset(&tree, 0, sizeof(tree));
    long_int x = 0;
    for (int i = 1 ; i <= n ; ++i)
    {
    	cin >> x;
    	pos = i;
    	val = x;
    	tree.update(1,1,n);
	}
	cin >> m;
	isSet = false;
    while(m--) {
      scanf("%d", &op);
      if(op == 1) {
        scanf("%d%d", &pos, &val);
        tree.update(1, 1, n);  
      } else {
        scanf("%d%d", &qL, &qR);  
        _min = INF , _max = -_min , _sum = 0;
        tree.query(1, 1, n);
        cout << _sum << endl; 
      }
    }
  
  return 0;
}

区间覆盖:

1、如何维护?

    首先想到多维护一个数组setv[],用来记录是否被某个数覆盖。

    然后我们就可以像区间查询一样,在O(lgn)时间里准确地给某段区域打上标记了。

    若每次的区间都正好一样是没问题,但万一出现交叉染色的情况怎么办?下传标记!

    什么意思?如果你打算给区间S染色,然而这段区间的一部分子区间Q已经染过色了,那么你把S区间的两个子区间给传递上这个染色标记,然后把S区间的标记设置为无标记。这样一来在递归的过程中可以确保该有的准确标记不丢失。

2、如何查询?

    首先基础框架还是和上面一样。只需要做点当标记存在时的特殊处理:

    如果查询的是最大最小值,那么只要比较当前的极值和标记上的值。

#include  
#define sz (R-L+1) 
#define NoPos -21000000
using namespace std;
typedef long long long_int; 
const int INF = 1000000000;
const int maxn = 500100;
const int maxnode = maxn << 2; 

int op, qL, qR, p, v;  
long_int _sum , _min , _max;

struct IntervalTree {
  long_int minv[maxnode] , maxv[maxnode] , sumv[maxnode] , setv[maxnode];
  
  void maintain(int o ,int L , int R)
  {
  	int lson = o << 1 , rson = lson | 1;
  	
  	if(R > L){
		sumv[o] = sumv[lson] + sumv[rson];
		minv[o] = min(minv[lson],minv[rson]);
		maxv[o] = max(maxv[lson],maxv[rson]);	  
	}
	if(setv[o] != NoPos)
	{
		minv[o] = maxv[o] = setv[o];
		sumv[o] = setv[o] * sz;
	}
  } 
  
  
  inline void pushdown(int o){
  	int lson = o << 1;
  	int rson = lson | 1;
  	if(setv[o] != NoPos){
  		setv[lson] = setv[rson] = setv[o];
  		setv[o] = NoPos;
  	}
  }

  void update(int o, int L, int R) {
  	int M = (L+R) >> 1;
  	int lson = o << 1 , rson = lson | 1;
  	if(qL <= L && qR >= R)
  		setv[o] = v;	
  	else
  	{
  		pushdown(o);
  		if(qL <= M)
  			update(lson,L,M);
  		else
  			maintain(lson,L,M);
  			
  		if(M < qR)
  			update(rson,M+1,R);
  		else
  			maintain(rson,M+1,R);
	}
	maintain(o,L,R);
  }

  void query(int o, int L, int R) {
    int M = (L+R) >> 1;
   	if(setv[o] != NoPos){
   		_sum += setv[o] * (min(R,qR) - max(L,qL) + 1);
		_max = max(_max,setv[o]);
		_min = min(_min,setv[o]);	
	} else if (qL <= L && qR >= R){
		_sum += sumv[o];
		_min = min(_min,minv[o]);
		_max = max(_max,maxv[o]);
	} else {
		int lson = o << 1 , rson = lson | 1;
		if(qL <= M)
			query(lson,L,M);
		if(M < qR)
			query(rson,M+1,R);
	}
  }
};


IntervalTree tree;

int main() {
  int n, m;
	cin >> n;
    memset(&tree, 0, sizeof(tree));
    long_int x = 0;
    for (int i = 1 ; i <= n ; ++i)
    {
    	cin >> x;
    	qL =  qR = i;
    	v = x;
    	tree.update(1,1,n);
	}
	cin >> m;
    while(m--) {
      scanf("%d", &op);
      if(op == 1) {
        scanf("%d%d%d", &qL, &qR,&v);
        tree.update(1, 1, n);  
      } else {
        scanf("%d%d", &qL, &qR);  
        _min = INF , _max = -_min , _sum = 0;
        tree.query(1, 1, n);
        cout << _sum << endl; 
      }
    }
  
  return 0;
}


这样一看区间加法是否也简单了呢?自行思考我要打卡去了再见。


            

 






你可能感兴趣的:(【数据结构】)