『数据结构』线段树板子

线段树学习前提须知

线段树的基础运用一般用于四个方面 单点修改 单点查询 区间修改 区间查询

且代码易于理解,虽代码量很长 但是并这不妨碍我们学习这个算法

算法内容

基本概念

1、线段树的形式类似于BST(二叉搜索树),在一些细节上区分于BST

2、线段树的基本思想:分治

3、每个节点都是以结构体的方式储存,结构体包含以下内容

  • 当前节点所包含的区间的左端点、右端点。

  • 这个区间所要维护的区间和(或其他 这里我们主要针对区间和)。

4、线段树的父亲节点都是其儿子节点的结合,其中根节点的区间就是整个数列

5、对于线段树:

  • 在树上的一个节点 \(k\),存在 \(k\) 的子节点分别为 \(k*2\)\(k*2 + 1\)

  • 对于这个节点 \(k\) ,它的左儿子的区间范围为 \([l, mid]\) ,它的右儿子为 \([mid + 1, r]\)

线段树代码与思路

结构体

const int N = ???;
struct Node {
	int l, r, w;
} tree[N << 2];

树记得开4倍空间

线段树的基础操作有 5 个:建树、单点查询、单点修改、区间查询、区间修改

建树

思路:

  • 给每一个节点,赋予区间的左端点和右端点
  • 对于每一个叶子节点,我们赋予它们已知的单独的值
  • 利用线段树的性质,向上合并
void build(int k, int l, int r) {
    tree[k].l = l; tree[k].r = r;
    if(l == r) {
        scanf("%d", &tree[k].w);
        return ;
    }
    
    int mid = (l + r) >> 1;
    build(k * 2, l, mid);
    build(k * 2 + 1, mid + 1, r);
    tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}

单点修改(我们这里假设需要修改的点编号为 X,需要添加的值为 T)

思路:

  • 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
  • 我们要修改的点一定在叶子节点上,查找这一部分需要消耗我们 \(O(logn)\)
  • 修改了一个点就会导致上面包含当前区间的点都要变,于是修改这一步也是 \(O(logn)\)
//Now X and T has been entered;
void change_point(int k) {
    if(tree[k].l == tree[k].r) {
        tree[k].w += t;
        return ;
    }
    
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= x) change_point(k * 2);
    else change_point(k * 2 + 1);
	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}

单点查询(这里设需要查询的点为 x)

思路:

  • 和单点修改类似 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
//Now x has been entered;
int ans;
void ask_point(int k) {
    if(tree[k].l == tree[k].r) {
        ans = tree[k].w;
        return ;
    }
    
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= x) ask_point(k * 2);
    else ask_point(k * 2 + 1);
}

区间查询(设查询的范围为 [l, r])

思路:

  • 和单点查询的思路类似,但这里换成了区间
//Now l and r has been entered
int ans;
void ask_interval(int k, int l, int r) {
    if(tree[k].l >= l && tree[k].r <= r) {
        ans += tree[k].w;
        return ;
    }
    
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= l) ask_interval(k * 2, l, r);
    if(mid < r) ask_interval(k * 2 + 1, l, r);
}

区间修改(※)

思路:

  • 单纯的寻找和修改一定会超时,我们这里引进一个新的操作叫 Lazy(懒操作)
    • 懒操作就像你打MC服务器,没有加载的区块是不会有任何行为的,但实际上这个区块上却存在方块和动物,当你加载了这个区块之后,上面的一些行为才正常化,比如熔炉烧东西或者是树苗长大。
    • 懒操作实际就是一种差分的思想,把一些信息存储到一些点上,若这个点被遍历了,那么当前点的信息一定会被子节点所继承,其中子节点上若有其他信息,那这个信息是不会消失的
//Now l,r,x has been entered
struct Node {
    int l, r, w;
    int f;
} tree[N >> 2];

void Lazydown(int k) {
    tree[k * 2].f += tree[k].f;
    tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
    tree[k * 2 + 1].f += tree[k].f;
    tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
    tree[k].f = 0;
}

void change_interval(int k) {
    if(tree[k].l >= l && tree[k].r <= r) {
        tree[k].w += (tree[k].r - tree[k].l + 1) * x;
        tree[k].f += x;
        return ;
    }
    
    if(tree[k].f) Lazydown(k);
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= l) change_interval(k * 2);
    if(mid < r) change_interval(k * 2 + 1);
    tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}

有几个问题需要注意

  • 懒标记的结构体需要更改,需要添加一个当前节点所存储的信息
  • 这里我们发现修改变成了 tree[k].w += (tree[k].r - tree[k].l + 1) * x;,这里你可以画图进行验证
  • Lazydown中代表的意思就是,我们已经遍历了编号为 \(k\) 的点,那么已经遍历过了之后,肯定会将信息传递
  • Lazydown的修改操作原理和区间修改中的相同,若不理解,可以画图进行深究
  • 因为我们已经传递和遍历了,那么当前点的信息就会被清空(不然又会回档了(游戏说法

加入了Lazydown之后,我们发现,只要遍历过的点都要进行懒操作,那么我们的所有代码都需要修改

全代码如下

//#define fre yes 信仰

#include <cstdio>

const int N = 200005;
struct Node {
    int l, r, w;
    int f;
} tree[N >> 2];

void build(int k, int l, int r) {
    tree[k].l = l; tree[k].r = r;
    if(l == r) {
        scanf("%d", &tree[k].w);
        return ;
    }
    
    int mid = (l + r) >> 1;
    build(k * 2, l, mid);
    build(k * 2 + 1, mid + 1, r);
   	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w
}

void Lazydown(int k) {
   	tree[k * 2].f += tree[k].f;
    tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
    tree[k * 2 + 1].f += tree[k].f;
    tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
    tree[k].f = 0;
}

void change_point(int k, int x) {
    if(tree[k].l == tree[k].r) {
        tree[k].w += x;
        return ;
    }
    
    if(tree[k].f) Lazydown(k); 
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= l) change_point(k * 2);
    else change_point(k * 2 + 1);
    tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}

int ans_p;
void ask_point(int k) {
    if(tree[k].l == tree[k].r) {
        ans_p = tree[k].w;
        return ;
    }
    
    if(tree[k].f) Lazydown(k);
    int mid = (tree[k].l + tree[k].r) >> 1;
    if(mid >= l) ask_point(k * 2);
    else ask_point(k * 2 + 1);
}

int ans_i;
void ask_interval(int k, int l, int r) {
    if(tree[k].l >= l && tree[k].r <= r) {
        ans_i += tree[k].w;
        return ;
    }
    
   	if(tree[k].f) Lazydown(k);
    int mid = (tree[k].r - tree[k].l) >> 1;
   	if(mid >= l) ask_interval(k * 2, l, r);
    if(mid < r) ask_interval(k * 2 + 1, l, r);
}

void change_interval(int k, int l, int r) {
    if(tree[k].l >= l && tree[k].r <= r) {
       tree[k].w += (tree[k].r - tree[k].l + 1) * x;
       tree[k].f += x;
       return ;
    }
    
	if(tree[k].f) Lazydown(k);
    int mid = (tree[k].r - tree[k].l) >> 1;
   	if(mid >= l) change_interval(k * 2, l, r);
    if(mid < r) chnage_interval(k * 2 + 1, l, r);
    tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
}

int main() {
    ...
}
posted @ 2019-08-26 10:43  Nicoppa  阅读(420)  评论(0编辑  收藏  举报